/*==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 "hsTypes.h"
#include "hsFastMath.h"
#include "hsTimer.h"

#include "plWaveSet7.h"
#include "plWaveSetShaderConsts.h"
#include "plRipVSConsts.h"

#include "plAccessGeometry.h"
#include "plAccessSpan.h"
#include "plAccessVtxSpan.h"

#include "plAuxSpan.h"
#include "plDynaDecal.h"
#include "plDynaRippleVSMgr.h"

#include "plMessage/plRenderMsg.h"
#include "pnMessage/plTimeMsg.h"

#include "pnMessage/plObjRefMsg.h"

#include "plgDispatch.h"
#include "plPipeline.h"

#include "hsResMgr.h"

#include "pnSceneObject/plDrawInterface.h"

#include "plPhysical.h"
#include "plMessage/plSimInfluenceMsg.h"

#include "plSurface/hsGMaterial.h"
#include "plDrawable/plDrawableSpans.h"
#include "plDrawable/plDrawableGenerator.h"

#include "plMessage/plAvatarMsg.h"
#include "plAvatar/plArmatureMod.h"

#include "plGImage/plMipmap.h"
#include "plGImage/plCubicEnvironmap.h"
#include "plSurface/plLayer.h"
#include "plMessage/plLayRefMsg.h"

#include "plSurface/plShader.h"

#include "plPipeline/plRenderTarget.h"
#include "plScene/plRenderRequest.h"
#include "plMessage/plRenderRequestMsg.h"
#include "plScene/plPageTreeMgr.h"

#include "plPipeline/plDynamicEnvMap.h"

#include "plGImage/plBumpMapGen.h"

#include "plMessage/plMatRefMsg.h"
#include "plMessage/plAgeLoadedMsg.h"

#include "plTweak.h"

#ifndef PLASMA_EXTERNAL_RELEASE
#include "plStatusLog/plStatusLog.h"
#include "plPipeline/plPlates.h"
#endif // PLASMA_EXTERNAL_RELEASE

using namespace plShaderID;

///////////////////////////////////////////////////////////////////////
#include "plProfile.h"
///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////

#define TEST_ENVSPH

// #define TEST_UVWS

static const hsScalar kPiOverTwo = hsScalarPI * 0.5f;

static const hsScalar kGravConst = 30.f;

static const hsScalar kOOEightNsqPI = 1.f / (8.f * hsScalarPI * 4.f * 4.f);
static hsScalar currOOEightNsqPI = kOOEightNsqPI;

static inline hsScalar FreqToLen(hsScalar f) { return 2.f * hsScalarPI / f; }
static inline hsScalar LenToFreq(hsScalar l) { return 2.f * hsScalarPI / l; }

static inline hsScalar MPH2FPS(hsScalar f) { return f * 5280.f / 3600.f; }
static inline hsScalar FPS2MPH(hsScalar f) { return f / 5280.f * 3600.f; }

plCONST(hsScalar) kTimeClamp(0.3f);

inline void plWorldWave7::Accumulate(hsPoint3& accumPos, hsVector3& accumNorm) const
{
    hsScalar dist = accumPos.fX * fDir.fX + accumPos.fY * fDir.fY;

    dist *= fFreq;
    dist += fPhase;

    hsScalar s, c;
    hsFastMath::SinCosAppr(dist, s, c);
//  s += 1.f;

    // Add scaled local Z instead? So decals can move up and down non-vertically?
    // Thing is, for the height is still based on the unperterbed position, so when
    // we move it laterally, the height is wrong. Maybe not too wrong, but still wrong.
    // Same for normal.
    accumPos.fZ += s * fAmplitude; 

    c *= -fFreq * fAmplitude;
    accumNorm.fX += fDir.fX * c;
    accumNorm.fY += fDir.fY * c;
}

#ifndef PLASMA_EXTERNAL_RELEASE
inline void plWaveSet7::GraphLen(hsScalar len) const
{
    if( fStatusGraph )
    {
        hsScalar maxLen = TexState().fMaxLength * kCompositeSize / State().fRippleScale;
        Int32 val = Int32(len / maxLen * 100.f);
        fStatusGraph->AddData(val);
    }
}

inline void plWaveSet7::IRestartGraph() const
{
    if( fStatusGraph )
        fStatusGraph->ClearData();
}

void plWaveSet7::StartGraph()
{
    delete fStatusGraph;
    plPlateManager::Instance().CreateGraphPlate(&fStatusGraph);
    fStatusGraph->SetSize(0.25f, 0.25f);
    fStatusGraph->SetDataRange(0, 100, kNumTexWaves);
    fStatusGraph->SetVisible(true);
    fStatusGraph->SetPosition(0.75f, -0.75f);
}

void plWaveSet7::StopGraph()
{
    delete fStatusGraph;
    fStatusGraph = nil;
}

inline void plWaveSet7::LogF(const char *format, ...) const
{
    if( fStatusLog )
    {
        va_list args;
        va_start(args,format);
        fStatusLog->AddLineV(format, args);
        va_end(args);
    }
}

inline void plWaveSet7::LogF(UInt32 color, const char *format, ...) const
{
    if( fStatusLog )
    {
        va_list args;
        va_start(args,format);
        fStatusLog->AddLineV(color, format, args);
        va_end(args);
    }
}

inline void plWaveSet7::IRestartLog() const
{
    if( fStatusLog )
        fStatusLog->Clear();
}

void plWaveSet7::StartLog()
{
    delete fStatusLog;
    fStatusLog = plStatusLogMgr::GetInstance().CreateStatusLog(kNumTexWaves, "TexWaves", 
                            plStatusLog::kDontWriteFile | plStatusLog::kDeleteForMe | plStatusLog::kFilledBackground);
}

void plWaveSet7::StopLog()
{
    delete fStatusLog;
    fStatusLog = nil;
}
#else // PLASMA_EXTERNAL_RELEASE
inline void plWaveSet7::GraphLen(hsScalar len) const
{
}

inline void plWaveSet7::IRestartGraph() const
{
}

void plWaveSet7::StartGraph()
{
}

void plWaveSet7::StopGraph()
{
}

inline void plWaveSet7::LogF(const char *format, ...) const
{
}

inline void plWaveSet7::LogF(UInt32 color, const char *format, ...) const
{
}

inline void plWaveSet7::IRestartLog() const
{
}

void plWaveSet7::StartLog()
{
}

void plWaveSet7::StopLog()
{
}
#endif // PLASMA_EXTERNAL_RELEASE


///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Enough of that, WaveSet (system manager) follows
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
plWaveSet7::plWaveSet7()
:   fLastTime(0),
    fCurrTime(0),
    fScrunchLen(8.f),
    fScrunchScale(2.f),
    fFreqScale(1.f),
    fRipVShader(nil),
    fRipPShader(nil),
    fShoreVShader(nil),
    fShorePShader(nil),
    fFixedVShader(nil),
    fFixedPShader(nil),
    fBiasVShader(nil),
    fBiasPShader(nil),
    fBumpMat(nil),
    fBumpDraw(nil),
    fBumpReq(nil),
    fBumpReqMsg(nil),
    fEnvMap(nil),
    fRefObj(nil),
    fCosineLUT(nil),
    fGraphShoreTex(nil),
    fBubbleShoreTex(nil),
    fEdgeShoreTex(nil),
    fMaxLen(0),

    fTrialUpdate(0),
    fTransistor(-1),
    fTransCountDown(30.f),
    fTransDel(0),

    fTexTrans(0),
    fTexTransCountDown(0),
    fTexTransDel(0),

    fStatusLog(nil),
    fStatusGraph(nil)
{
    IInitState();
    IInitWaveConsts();

    int i;
    for( i = 0; i < kNumWaves; i++ )
    {
        fFreqMod[i] = 1.f;
        IInitWave(i);
    }
    for( i = 0; i < 4; i++ )
    {
        fFixedLayers[i] = nil;
    }
    for( i = 0; i < kNumBumpShaders; i++ )
    {
        fBumpVShader[i] = nil;
        fBumpPShader[i] = nil;
    }
    fBiasLayer[0] = nil;
    fBiasLayer[1] = nil;
    for( i = 0; i < kNumTexWaves; i++ )
    {
        fBumpLayers[i] = nil;
        fTexWaveFade[i] = 1.f;
    }
    for( i = 0; i < kGraphShorePasses; i++ )
    {
        fGraphVShader[i] = nil;
        fGraphPShader[i] = nil;

        fGraphShoreMat[i] = nil;
        fGraphShoreRT[i] = nil;
        fGraphShoreDraw[i] = nil;
        fGraphReq[i] = nil;
        fGraphReqMsg[i] = nil;
    }
    for( i = 0; i < kNumDecalVShaders; i++ )
        fDecalVShaders[i] = nil;
    for( i = 0; i < kNumDecalPShaders; i++ )
        fDecalPShaders[i] = nil;
}

plWaveSet7::~plWaveSet7()
{
    delete fStatusLog;

    delete fBumpReqMsg;
    delete fBumpReq;

    int i;
    for( i = 0; i < kGraphShorePasses; i++ )
    {
        delete fGraphReqMsg[i];
        delete fGraphReq[i];
    }
}

void plWaveSet7::Read(hsStream* stream, hsResMgr* mgr)
{
    plMultiModifier::Read(stream, mgr);

    fMaxLen = stream->ReadSwapScalar();

    fState.Read(stream);
    IUpdateWindDir(0);

    int n = stream->ReadSwap32();
    int i;
    for( i = 0; i < n; i++ )
    {
        mgr->ReadKeyNotifyMe(stream, TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnCreate, -1, kRefShore), plRefFlags::kPassiveRef);
    }
    n = stream->ReadSwap32();
    for( i = 0; i < n; i++ )
    {
        mgr->ReadKeyNotifyMe(stream, TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnCreate, -1, kRefDecal), plRefFlags::kPassiveRef);
    }
    mgr->ReadKeyNotifyMe(stream, TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnCreate, -1, kRefEnvMap), plRefFlags::kActiveRef);

    if( HasFlag(kHasRefObject) )
    {
        mgr->ReadKeyNotifyMe(stream, TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnCreate, -1, kRefRefObj), plRefFlags::kPassiveRef);
    }

    ISetupTextureWaves();

    for( i = 0; i < kNumWaves; i++ )
    {
        fFreqMod[i] = 1.f;
        IInitWave(i);
    }

    plgDispatch::Dispatch()->RegisterForExactType(plEvalMsg::Index(), GetKey());
    plgDispatch::Dispatch()->RegisterForExactType(plRenderMsg::Index(), GetKey());
    plgDispatch::Dispatch()->RegisterForExactType(plInitialAgeStateLoadedMsg::Index(), GetKey());
    plgDispatch::Dispatch()->RegisterForExactType(plAgeLoadedMsg::Index(), GetKey());
}

void plWaveSet7::Write(hsStream* stream, hsResMgr* mgr)
{
    plMultiModifier::Write(stream, mgr);

    stream->WriteSwapScalar(fMaxLen);

    fState.Write(stream);

    stream->WriteSwap32(fShores.GetCount());
    int i;
    for( i = 0; i < fShores.GetCount(); i++ )
    {
        mgr->WriteKey(stream, fShores[i]);
    }
    stream->WriteSwap32(fDecals.GetCount());
    for( i = 0; i < fDecals.GetCount(); i++ )
    {
        mgr->WriteKey(stream, fDecals[i]);
    }
    mgr->WriteKey(stream, fEnvMap);

    if( HasFlag(kHasRefObject) )
    {
        mgr->WriteKey(stream, fRefObj);
    }
}

hsBool plWaveSet7::MsgReceive(plMessage* msg)
{
    plEvalMsg* update = plEvalMsg::ConvertNoRef(msg);
    if( update )
    {
        if (fFixedVShader == nil)
        {
            ICheckTargetMaterials();
            ICheckShoreMaterials();
            ICheckDecalMaterials();
        }
        IRestartLog();
        IRestartGraph();

        plCONST(hsBool) reRender(false);
        if( reRender || (fTrialUpdate & kReRenderEnvMap) )
        {
            plDynamicEnvMap* envMap = plDynamicEnvMap::ConvertNoRef(fEnvMap);
            if( envMap )
            {
                envMap->ReRender();

                fTrialUpdate &= ~kReRenderEnvMap;
            }
        }

        hsScalar dt = update->DelSeconds();
        if( dt > kTimeClamp )
            dt = kTimeClamp;

        IUpdateWindDir(dt);

        IFloatBuoys(dt);
        return true;
    }

    plRenderMsg* rend = plRenderMsg::ConvertNoRef(msg);
    if( rend )
    {
        if( !IAnyBoundsVisible(rend->Pipeline()) )
            return true;

        fCurrTime = hsTimer::GetSysSeconds();
        // Can't just use GetDelSysSeconds() or else we lose time if we skip a frame render because of high FPS.
        hsScalar dt = fLastTime > 0 ? hsScalar(fCurrTime - fLastTime) : hsTimer::GetDelSysSeconds();
        if( dt > kTimeClamp )
            dt = kTimeClamp;

        IUpdateRefObject();

        IUpdateLayers(dt);

        IUpdateWaves(dt);

        hsMatrix44 l2w;
        hsMatrix44 w2l;
        IUpdateShaders(rend->Pipeline(), l2w, w2l);
        IUpdateGraphShaders(rend->Pipeline(), dt);

        fLastTime = fCurrTime;
        return true;
    }

    plAgeLoadedMsg* ageLoaded = plAgeLoadedMsg::ConvertNoRef(msg);
    if( (ageLoaded && ageLoaded->fLoaded) || plInitialAgeStateLoadedMsg::ConvertNoRef(msg) )
    {
        ICheckTargetMaterials();
        ICheckShoreMaterials();
        ICheckDecalMaterials();
        return true;
    }

    plGenRefMsg* refMsg = plGenRefMsg::ConvertNoRef(msg);
    if( refMsg )
    {
        if( refMsg->GetContext() & (plRefMsg::kOnCreate | plRefMsg::kOnRequest | plRefMsg::kOnReplace) )
        {
            IOnReceive(refMsg);
        }
        else
        {
            IOnRemove(refMsg);
        }
        return true;
    }

    return plMultiModifier::MsgReceive(msg);
}

hsBool plWaveSet7::IAnyBoundsVisible(plPipeline* pipe) const
{
    int i;
    for( i = 0; i < fTargBnds.GetCount(); i++ )
    {
        if( pipe->TestVisibleWorld(fTargBnds[i]) )
            return true;
    }
    return false;
}

hsBool plWaveSet7::IOnReceive(plGenRefMsg* refMsg)
{
    switch( refMsg->fType )
    {
    case kRefRefObj:
        fRefObj = plSceneObject::ConvertNoRef(refMsg->GetRef());
        return true;
    case kRefCosineLUT:
        fCosineLUT = plMipmap::ConvertNoRef(refMsg->GetRef());
        return true;
    case kRefEnvMap:
        fEnvMap = plBitmap::ConvertNoRef(refMsg->GetRef());
        return true;
    case kRefShore:
        fShores.Append(plSceneObject::ConvertNoRef(refMsg->GetRef()));
        return true;
    case kRefDecal:
        fDecals.Append(plSceneObject::ConvertNoRef(refMsg->GetRef()));
        return true;
    case kRefDecVShader:
        fDecalVShaders[refMsg->fWhich] = plShader::ConvertNoRef(refMsg->GetRef());
        return true;
    case kRefDecPShader:
        fDecalPShaders[refMsg->fWhich] = plShader::ConvertNoRef(refMsg->GetRef());
        return true;
    case kRefGraphShoreRT:
        fGraphShoreRT[refMsg->fWhich] = plRenderTarget::ConvertNoRef(refMsg->GetRef());
        return true;
    case kRefGraphVShader:
        fGraphVShader[refMsg->fWhich] = plShader::ConvertNoRef(refMsg->GetRef());
        return true;
    case kRefGraphPShader:
        fGraphPShader[refMsg->fWhich] = plShader::ConvertNoRef(refMsg->GetRef());
        return true;
    case kRefGraphShoreTex:
        fGraphShoreTex = plMipmap::ConvertNoRef(refMsg->GetRef());
        return true;
    case kRefBubbleShoreTex:
        fBubbleShoreTex = plMipmap::ConvertNoRef(refMsg->GetRef());
        return true;
    case kRefEdgeShoreTex:
        fEdgeShoreTex = plMipmap::ConvertNoRef(refMsg->GetRef());
        return true;
    case kRefGraphShoreMat:
        fGraphShoreMat[refMsg->fWhich] = hsGMaterial::ConvertNoRef(refMsg->GetRef());
        return true;
    case kRefGraphShoreDraw:
        fGraphShoreDraw[refMsg->fWhich] = plDrawableSpans::ConvertNoRef(refMsg->GetRef());
        return true;
    case kRefBiasVShader:
        fBiasVShader = plShader::ConvertNoRef(refMsg->GetRef());
        return true;
    case kRefBiasPShader:
        fBiasPShader = plShader::ConvertNoRef(refMsg->GetRef());
        return true;
    case kRefBumpVShader:
        fBumpVShader[refMsg->fWhich] = plShader::ConvertNoRef(refMsg->GetRef());
        return true;
    case kRefBumpPShader:
        fBumpPShader[refMsg->fWhich] = plShader::ConvertNoRef(refMsg->GetRef());
        return true;
    case kRefRipVShader:
        fRipVShader = plShader::ConvertNoRef(refMsg->GetRef());
        return true;
    case kRefRipPShader:
        fRipPShader = plShader::ConvertNoRef(refMsg->GetRef());
        return true;
    case kRefShoreVShader:
        fShoreVShader = plShader::ConvertNoRef(refMsg->GetRef());
        return true;
    case kRefShorePShader:
        fShorePShader = plShader::ConvertNoRef(refMsg->GetRef());
        return true;
    case kRefFixedVShader:
        fFixedVShader = plShader::ConvertNoRef(refMsg->GetRef());
        return true;
    case kRefFixedPShader:
        fFixedPShader = plShader::ConvertNoRef(refMsg->GetRef());
        return true;
    case kRefBumpDraw:
        fBumpDraw = plDrawableSpans::ConvertNoRef(refMsg->GetRef());
        return true;
    case kRefBumpMat:
        fBumpMat = hsGMaterial::ConvertNoRef(refMsg->GetRef());
        return true;
    case kRefDynaDecalMgr:
        {
            plDynaDecalMgr* dyna = plDynaDecalMgr::ConvertNoRef(refMsg->GetRef());
            if( fDecalMgrs.Find(dyna) == fDecalMgrs.kMissingIndex )
            {
                fDecalMgrs.Append(dyna);
            }
        }
        return true;
    case kRefBuoy:
        {
            plSceneObject* so = plSceneObject::ConvertNoRef(refMsg->GetRef());
            if( fBuoys.Find(so) == fBuoys.kMissingIndex )
            {
                IShiftCenter(so);
                fBuoys.Append(so);
            }
        }
        return true;
    }
    return false;
}

hsBool plWaveSet7::IOnRemove(plGenRefMsg* refMsg)
{
    switch( refMsg->fType )
    {
    case kRefRefObj:
        fRefObj = nil;
        return true;
    case kRefCosineLUT:
        fCosineLUT = nil;
        return true;
    case kRefEnvMap:
        fEnvMap = nil;
        return true;
    case kRefShore:
        {
            plSceneObject* shore = (plSceneObject*)refMsg->GetRef();
            int idx = fShores.Find(shore);
            if( idx != fShores.kMissingIndex )
                fShores.Remove(idx);
        }
        return true;
    case kRefDecal:
        {
            plSceneObject* decal = (plSceneObject*)refMsg->GetRef();
            int idx = fDecals.Find(decal);
            if( idx != fDecals.kMissingIndex )
                fDecals.Remove(idx);
        }
        return true;
    case kRefDecVShader:
        fDecalVShaders[refMsg->fWhich] = nil;
        return true;
    case kRefDecPShader:
        fDecalPShaders[refMsg->fWhich] = nil;
        return true;
    case kRefGraphShoreRT:
        fGraphShoreRT[refMsg->fWhich] = nil;
        return true;
    case kRefGraphVShader:
        fGraphVShader[refMsg->fWhich] = nil;
        return true;
    case kRefGraphPShader:
        fGraphPShader[refMsg->fWhich] = nil;
        return true;
    case kRefGraphShoreTex:
        fGraphShoreTex = nil;
        return true;
    case kRefBubbleShoreTex:
        fBubbleShoreTex = nil;
        return true;
    case kRefEdgeShoreTex:
        fEdgeShoreTex = nil;
        return true;
    case kRefGraphShoreMat:
        fGraphShoreMat[refMsg->fWhich] = nil;
        return true;
    case kRefGraphShoreDraw:
        fGraphShoreDraw[refMsg->fWhich] = nil;
        return true;
    case kRefBiasVShader:
        fBiasVShader = nil;
        return true;
    case kRefBiasPShader:
        fBiasPShader = nil;
        return true;
    case kRefBumpVShader:
        fBumpVShader[refMsg->fWhich] = nil;
        return true;
    case kRefBumpPShader:
        fBumpPShader[refMsg->fWhich] = nil;
        return true;
    case kRefRipVShader:
        fRipVShader = nil;
        return true;
    case kRefRipPShader:
        fRipPShader = nil;
        return true;
    case kRefShoreVShader:
        fShoreVShader = nil;
        return true;
    case kRefShorePShader:
        fShorePShader = nil;
        return true;
    case kRefFixedVShader:
        fFixedVShader = nil;
        return true;
    case kRefFixedPShader:
        fFixedPShader = nil;
        return true;
    case kRefBumpDraw:
        fBumpDraw = nil;
        return true;
    case kRefBumpMat:
        fBumpMat = nil;
        return true;
    case kRefDynaDecalMgr:
        {
            plDynaDecalMgr* dyna = (plDynaDecalMgr*)refMsg->GetRef();
            int idx = fDecalMgrs.Find(dyna);
            if( fDecalMgrs.kMissingIndex != idx )
                fDecalMgrs.Remove(idx);
        }
        return true;
    case kRefBuoy:
        {
            plSceneObject* so = (plSceneObject*)refMsg->GetRef();
            int idx = fBuoys.Find(so);
            if( fBuoys.kMissingIndex != idx )
                fBuoys.Remove(idx);
        }
        return true;
    }
    return false;
}

void plWaveSet7::SetState(const plFixedWaterState7& state, hsScalar dur)
{ 
    fState.Set(state, dur);

    if( fFixedLayers[0] )
    {
        plLayer* lay = plLayer::ConvertNoRef(fFixedLayers[0]->BottomOfStack());
        lay->SetRuntimeColor(state.fWaterTint);
    }
}

void plWaveSet7::IUpdateWaves(hsScalar dt)
{
    ITransition(dt);
    ITransTex(dt);
    ICalcScale();
    fScrunchLen = 1.e33f;

    if( fTrialUpdate & kReInitWaves )
    {
        IReInitWaves();
        ISetupTextureWaves();
        ICreateBumpMipmapPS();
    }

    int i;
    for( i = 0; i < kNumWaves; i++ )
    {
        IUpdateWave(dt, i);
    }
}

// return true if we've finished this transition.
hsBool plWaveSet7::ITransContinue(hsScalar dt)
{
    hsScalar currFade = (fFreqMod[fTransistor] += fTransDel * dt);

    if( currFade <= 0 )
    {
        // Done fading down, time to fade back up.
        fTransDel = -fTransDel;
        fFreqMod[fTransistor] = fTransDel * dt;

        // Reinit the wave. This gives a chance to introduce more randomization,
        // as well as react to changing weather conditions.
        IInitWave(fTransistor);

        return false;
    }
    
    if( currFade >= 1.f )
    {
        fFreqMod[fTransistor] = 1.f;
        fTransDel = 0;

        return true;
    }

    return false;
}

void plWaveSet7::IStartTransition(hsScalar dt)
{
    // select the next wave for transitioning
    if( ++fTransistor >= kNumWaves )
        fTransistor = 0;

    // set the transFade to be fading down.
    plCONST(hsScalar) kTransDel(0.5f);
    fTransDel = -kTransDel;
}

hsScalar plWaveSet7::ITransitionDelay() const
{
    plCONST(hsScalar) kTransDelay(2.f);
    return kTransDelay;
}

void plWaveSet7::ITransition(hsScalar dt)
{
    // If we're in a transition, keep transitioning till it's done.
    if( fTransDel != 0 )
    {
        if( ITransContinue(dt) )
            fTransCountDown = ITransitionDelay();
    }
    // else if our transition countdown has gone to zero, start a new one.
    else if( (fTransCountDown -= dt) <= 0 )
    {
        IStartTransition(dt);
    }

}

hsBool plWaveSet7::ITransTexContinue(hsScalar dt)
{
    hsScalar currFade = (fTexWaveFade[fTexTrans] += fTexTransDel * dt);

    if( currFade <= 0 )
    {
        // Done fading down, time to fade back up.
        fTexTransDel = -fTexTransDel;
        fTexWaveFade[fTexTrans] = fTexTransDel * dt;

        // ReInit the wave
        IInitTexWave(fTexTrans);

        return false;
    }

    if( currFade >= 1.f )
    {
        // This one is back to full on, time to pick on another one.
        fTexWaveFade[fTexTrans] = 1.f;
        fTexTransDel = 0;

        return true;
    }

    return false;
}

void plWaveSet7::IStartTexTransition(hsScalar dt)
{
    if( ++fTexTrans >= kNumTexWaves )
        fTexTrans = 0;

    plConst(hsScalar) kTexTransDel(4.f);
    fTexTransDel = -kTexTransDel;
}

void plWaveSet7::ITransTex(hsScalar dt)
{

    // If we're in a transition, keep with it.
    if( fTexTransDel != 0 )
    {
        plConst(hsScalar) kTexTransDelay(0);
        if( ITransTexContinue(dt) )
            fTexTransCountDown = kTexTransDelay;
    }
    // else if it's time to start another...
    else if( (fTexTransCountDown -= dt) <= 0 )
    {
        IStartTexTransition(dt);
    }
}

void plWaveSet7::ICalcScale()
{
}

void plWaveSet7::IUpdateWave(hsScalar dt, int i)
{
    plWorldWave7& wave = fWorldWaves[i];

    hsScalar len = FreqToLen(wave.fFreq);

    hsScalar speed = hsFastMath::InvSqrtAppr(len / (2.f * hsScalarPI * kGravConst));

    static hsScalar speedHack = 1.f;
    speed *= speedHack;
    wave.fPhase += speed * dt;
//  wave.fPhase = fmod( speed * t, 2.f * hsScalarPI);

    hsScalar amp = GeoState().fAmpOverLen * len / hsScalar(kNumWaves);

    amp *= fFreqMod[i] * fFreqScale;

    wave.fAmplitude = amp;

}

void plWaveSet7::IReInitWaves()
{
    int i;
    for( i = 0; i < kNumWaves; i++ )
    {
        IInitWave(i);
    }
    fTransDel = 0;
    fTransCountDown = ITransitionDelay();
    fTrialUpdate &= ~kReInitWaves;
}


void plWaveSet7::IInitWave(int i)
{
    plWorldWave7& wave = fWorldWaves[i];

    wave.fLength = GeoState().fMinLength + fRand.RandZeroToOne() * (GeoState().fMaxLength - GeoState().fMinLength);

    hsScalar len = wave.fLength;

    wave.fFreq = LenToFreq(len);

    wave.fPhase = 0;

    wave.fAmplitude = 0;

    // Figure out the direction based on wind direction.
    // Even waves go in the wind direction,
    // odd waves go opposite direction
    plConst(hsScalar) kMinRotDeg(15.f);
    plConst(hsScalar) kMaxRotDeg(180.f);
    hsVector3 dir = fWindDir;

    hsScalar rotBase = GeoState().fAngleDev;

    hsScalar rads = rotBase * fRand.RandMinusOneToOne();
    hsScalar rx = hsScalar(cosf(rads));
    hsScalar ry = hsScalar(sinf(rads));

    hsScalar x = dir.fX;
    hsScalar y = dir.fY;
    dir.fX = x * rx + y * ry;
    dir.fY = x * -ry + y * rx;

    wave.fDir.Set(dir.fX, dir.fY, 0);
}

inline void plWaveSet7::IScrunch(hsPoint3& pos, hsVector3& norm) const
{
    pos.fX += -norm.fX * fScrunchLen;
    pos.fY += -norm.fY * fScrunchLen;

    norm.fX *= fScrunchScale;
    norm.fY *= fScrunchScale;

    hsFastMath::NormalizeAppr(norm);
}

hsScalar plWaveSet7::EvalPoint(hsPoint3& pos, hsVector3& norm)
{
    hsPoint3 accumPos;
    hsVector3 accumNorm;
    accumPos.Set(pos.fX, pos.fY, State().fWaterHeight);
    accumNorm.Set(0,0,0);

    int i;
    for( i = 0; i < kNumWaves; i++ )
        fWorldWaves[i].Accumulate(accumPos, accumNorm);

    accumNorm.fZ = 1.f;

    hsFastMath::NormalizeAppr(accumNorm);

    IScrunch(accumPos, accumNorm);

    // Project original pos along Z onto the plane tangent at accumPos with norm accumNorm
    hsScalar t = hsVector3(&accumPos, &pos).InnerProduct(accumNorm);
    t /= accumNorm.fZ;

    pos.fZ += t;

    norm = accumNorm;

    return pos.fZ;
}

void plWaveSet7::IUpdateWindDir(hsScalar dt)
{
    fWindDir = -State().fWindDir;
    hsFastMath::NormalizeAppr(fWindDir);
}

void plWaveSet7::IUpdateRefObject()
{
    if( fRefObj )
    {
        hsMatrix44 l2w = fRefObj->GetLocalToWorld();
        
        hsScalar h = l2w.fMap[2][3];

        hsScalar x = -l2w.fMap[0][1];
        hsScalar y = -l2w.fMap[1][1];

        fState.fWaterHeight = h;

        fState.fWindDir = hsVector3(x, y, 0.f);
    }
}

void plWaveSet7::IFloatBuoy(hsScalar dt, plSceneObject* so)
{
    // Compute force based on world bounds
    hsBounds3Ext wBnd = so->GetDrawInterface()->GetWorldBounds();
    hsBounds3Ext lBnd = so->GetDrawInterface()->GetLocalBounds();

    hsPoint3 pos(wBnd.GetCenter());
    hsPoint3 surfPos(pos);
    hsVector3 surfNorm;

    EvalPoint(surfPos, surfNorm);

    // Direction of impulse is surfNorm. Magnitude is proportional to depth
    // (in an approximation lazy hackish way).
    hsPoint2 boxDepth;
    wBnd.TestPlane(surfNorm, boxDepth);
    hsScalar surfDepth = surfNorm.InnerProduct(surfPos);
    hsScalar depth = surfDepth - boxDepth.fX;

    if( depth < 0 )
        return;

    if( depth > wBnd.GetMaxs().fZ - wBnd.GetMins().fZ )
        depth = wBnd.GetMaxs().fZ - wBnd.GetMins().fZ;

    // We really want the cross section area as facing into the water,
    // but life is full of little disappointments.
    hsScalar area = (wBnd.GetMaxs().fX - wBnd.GetMins().fX) * (wBnd.GetMaxs().fY - wBnd.GetMins().fY);

    hsScalar volume = area * depth;

    plCONST(hsScalar) kWaterDensity(1.0f);
    hsScalar forceMag = volume * kWaterDensity;

    // surfNorm is now the impulse vector. But where to apply it.
    // Don't currently have anything informative from the physical to use.
    // So, let's fake something for the moment.

    plKey physKey = so->GetSimulationInterface()->GetPhysical()->GetKey();

//  plImpulseMsg* iMsg = TRACKED_NEW plImpulseMsg(GetKey(), physKey, hsVector3(0, 0, 1.f) * forceMag * dt);
//  iMsg->Send();

#if 0
    plCONST(hsScalar) kRotScale(1.f);
    hsVector3 rotAx = hsVector3(0, 0, 1.f) % surfNorm;
    rotAx *= kRotScale * dt * volume;

    plAngularImpulseMsg* aMsg = TRACKED_NEW plAngularImpulseMsg(GetKey(), physKey, rotAx);
    aMsg->Send();
#endif

    plCONST(hsScalar) kDampener(0.1f);
    plCONST(hsScalar) kBaseDamp(0.1f);
    if( wBnd.GetMaxs().fZ > wBnd.GetMins().fZ )
    {
        // Remember, we've already limited depth to be <= Max.fZ - Min.fZ;
        hsScalar damp = depth / (wBnd.GetMaxs().fZ - wBnd.GetMins().fZ);
        damp *= kDampener;
        damp += kBaseDamp;

//      plDampMsg* dMsg = TRACKED_NEW plDampMsg(GetKey(), physKey, damp);
//      dMsg->Send();
    }
}

void plWaveSet7::IFloatBuoys(hsScalar dt)
{
    int i;
    for( i = 0; i < fBuoys.GetCount(); i++ )
    {
        if( fBuoys[i] && fBuoys[i]->GetSimulationInterface() && fBuoys[i]->GetSimulationInterface()->GetPhysical() && fBuoys[i]->GetDrawInterface() )
        {
            IFloatBuoy(dt, fBuoys[i]);
        }
    }
}

void plWaveSet7::IShiftCenter(plSceneObject* so) const
{
    // HACKAGE
    if( 0 && so->GetSimulationInterface() && so->GetSimulationInterface()->GetPhysical() && so->GetDrawInterface() )
    {
        hsPoint3 center = so->GetDrawInterface()->GetWorldBounds().GetCenter();
        hsPoint3 pos = so->GetLocalToWorld().GetTranslate();
        hsVector3 offset(&pos, &center);
//      plShiftMassMsg* msg = TRACKED_NEW plShiftMassMsg(GetKey(), so->GetSimulationInterface()->GetPhysical()->GetKey(), offset);
//      msg->Send();
    }
}

void plWaveSet7::ICheckTargetMaterials()
{
    hsBounds3Ext targBnd;
    targBnd.MakeEmpty();
    int i;
    for( i = 0; i < GetNumTargets(); i++ )
    {
        plSceneObject* so = GetTarget(i);
        if( !so )
            continue;

        const plDrawInterface* di = so->GetDrawInterface();
        if( !di )
            continue;

        hsTArray<plAccessSpan> src;
        plAccessGeometry::Instance()->OpenRO(di, src, false);

        const int numUVWs = src.GetCount() && src[0].AccessVtx().HasUVWs() ? src[0].AccessVtx().NumUVWs() : 0;
        int j;
        for( j = 0; j < src.GetCount(); j++ )
        {
            hsAssert(src[j].AccessVtx().NumUVWs() == numUVWs, "Must have same number uvws on each water mesh");
            ICreateFixedMat(src[j].GetMaterial(), numUVWs); // no-op if it's already setup.

            targBnd.Union(&src[j].GetWorldBounds());
        }

        plAccessGeometry::Instance()->Close(src);

        for( j = 0; j < di->GetNumDrawables(); j++ )
        {
            plDrawableSpans* dr = plDrawableSpans::ConvertNoRef(di->GetDrawable(j));
            if( dr )
            {
                dr->SetNativeProperty(di->GetDrawableMeshIndex(j), plSpan::kPropRunTimeLight | plSpan::kPropSkipProjection | plSpan::kPropNoShadow | plSpan::kLiteVtxNonPreshaded | plSpan::kPropReverseSort, true);
            }
        }
    }
    if( targBnd.GetType() == kBoundsNormal )
    {
        if( !fTargBnds.GetCount() )
            fTargBnds.SetCount(1);
        
        plConst(hsScalar) kMaxWaveHeight(5.f);

        hsPoint3 p;
        p = targBnd.GetMins();
        p.fZ = GetHeight() - kMaxWaveHeight;
        fTargBnds[0].Reset(&p);
        p = targBnd.GetMaxs();
        p.fZ = GetHeight() + kMaxWaveHeight;
        fTargBnds[0].Union(&p);
    }
}


void plWaveSet7::IAddTarget(const plKey& key)
{
    plObjRefMsg* refMsg = TRACKED_NEW plObjRefMsg(key, plRefMsg::kOnRequest, -1, plObjRefMsg::kModifier);
    hsgResMgr::ResMgr()->AddViaNotify( GetKey(), refMsg, plRefFlags::kActiveRef);
}

void plWaveSet7::IRemoveTarget(const plKey& key)
{
    plObjRefMsg* refMsg = TRACKED_NEW plObjRefMsg(key, plRefMsg::kOnRemove, -1, plObjRefMsg::kModifier);
    refMsg->SetRef(this);
    refMsg->Send();
}

void plWaveSet7::AddTarget(const plKey& key)
{
    IAddTarget(key);
}

void plWaveSet7::RemoveTarget(const plKey& key)
{
    IRemoveTarget(key);
}

void plWaveSet7::SetRefObject(plSceneObject* refObj)
{
    fFlags.SetBit(kHasRefObject, refObj != nil);

    plGenRefMsg* msg = TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnRequest, 0, kRefRefObj);
    hsgResMgr::ResMgr()->SendRef(refObj, msg, plRefFlags::kPassiveRef);
}

void plWaveSet7::AddBuoy(plKey soKey)
{
    plGenRefMsg* msg = TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnRequest, 0, kRefBuoy);
    hsgResMgr::ResMgr()->AddViaNotify(soKey, msg, plRefFlags::kPassiveRef);
}

void plWaveSet7::RemoveBuoy(plKey soKey)
{
    plGenRefMsg* msg = TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnRemove, 0, kRefBuoy);
    msg->SetRef(soKey->ObjectIsLoaded());
    msg->Send();
}



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

// This is very evil. I can't believe I'm doing this. Stop me, please.
class plFilterMask
{
    protected:
        int             fExt;
        hsScalar        **fMask;

    public:

        plFilterMask( hsScalar sig );
        virtual ~plFilterMask();

        int     Begin() const { return -fExt; }
        int     End() const { return fExt; }

        hsScalar    Mask( int i, int j ) const { return fMask[ i ][ j ]; }
};
// End evil.

void plWaveSet7::IInitState()
{
    plConst(hsScalar) kWaterTable(-10.f);
    plConst(hsScalar) kWaterOffset[3] = { 3.f, 3.f, 0.f };
    plConst(hsScalar) kMaxAtten[3] = { 1.f, 1.f, 1.f };
    plConst(hsScalar) kMinAtten[3] = { 0.f, 0.f, 0.f };
    plConst(hsScalar) kDepthFalloff[3] = { 4.f, 4.f, 6.f };

    plConst(hsScalar) kGeoMinLen(3.f);
    plConst(hsScalar) kGeoMaxLen(8.f);
    plConst(hsScalar) kGeoAmpOverLen(0.1f);
    plConst(hsScalar) kGeoAngleDev(30.f * hsScalarPI / 180.f);
    plConst(hsScalar) kGeoChop(1.f);

    plConst(hsScalar) kTexMinLen(4.f);
    plConst(hsScalar) kTexMaxLen(30.f);
    plConst(hsScalar) kTexAmpOverLen(0.1f);
    plConst(hsScalar) kTexAngleDev(30.f * hsScalarPI / 180.f);
    plConst(hsScalar) kTexChop(1.f);

    plFixedWaterState7 state;

    state.fWindDir = hsVector3(0, 1.f, 0);

    state.fGeoState.fMaxLength = kGeoMaxLen;
    state.fGeoState.fMinLength = kGeoMinLen;
    state.fGeoState.fAmpOverLen = kGeoAmpOverLen;
    state.fGeoState.fChop = kGeoChop;
    state.fGeoState.fAngleDev = kGeoAngleDev;
    
    state.fTexState.fMaxLength = kTexMaxLen;
    state.fTexState.fMinLength = kTexMinLen;
    state.fTexState.fAmpOverLen = kTexAmpOverLen;
    state.fTexState.fChop = kTexChop;
    state.fTexState.fAngleDev = kTexAngleDev;
    
    state.fRippleScale = 25.f;

    plConst(hsScalar) kNoise(1.f);
    plConst(hsScalar) kSpecStart(50.f);
    plConst(hsScalar) kSpecEnd(10000.f);

    hsVector3 spec;
    spec[state.kNoise] = kNoise;
    spec[state.kSpecStart] = kSpecStart;
    spec[state.kSpecEnd] = kSpecEnd;
    state.fSpecVec = spec;

    state.fWaterHeight = kWaterTable;
    state.fWaterOffset = hsVector3(kWaterOffset[0], kWaterOffset[1], kWaterOffset[2]);
    state.fMaxAtten = hsVector3(kMaxAtten[0], kMaxAtten[1], kMaxAtten[2]);
    state.fMinAtten = hsVector3(kMinAtten[0], kMinAtten[1], kMinAtten[2]);
    state.fDepthFalloff = hsVector3(kDepthFalloff[0], kDepthFalloff[1], kDepthFalloff[2]);

    state.fWispiness = 0;
    state.fShoreTint = hsColorRGBA().Set(1.f, 1.f, 1.f, 1.f);
    state.fMaxColor = hsColorRGBA().Set(1.f, 1.f, 1.f, 1.f);
    state.fMinColor = hsColorRGBA().Set(0.2f, 0.4f, 0.4f, 0.6f);
    state.fEdgeOpac = 1.f;
    state.fEdgeRadius = 1.f;

    state.fPeriod = 3.f;
    state.fFingerLength = 1.f;

    state.fWaterTint = hsColorRGBA().Set(0.1f, 0.2f, 0.2f, 1.f);
    state.fSpecularTint = hsColorRGBA().Set(1.f, 1.f, 1.f, 1.f);

    state.fEnvCenter = hsPoint3(0,0,0);
    state.fEnvRadius = 500.f;
    state.fEnvRefresh = 0.f;

    fState = state;

    IUpdateWindDir(0);
}


void plWaveSet7::IInitWaveConsts()
{
    // See header for what fS, fK and fD actually mean.
    WaveK   waveKs[] = {
        // fS       fK      fD
        { 1.f,      5.f,    0.f },
        { 1.f,      10.f,   2.f },
        { -1.f,     20.f,   -9.f }, 
        { 1.f,      30.f,   16.f }
    };
    int i;
    for( i = 0; i < kNumTexWaves; i++ )
        fWaveKs[i] = waveKs[i];

    TexWaveWindDep windDeps[] = {
        // WindSpeed, Height, Specular
        { 0.f,      0.01f,      1.f },
        { 5.f,      0.05f,      0.8f },
        { 10.f,     0.05f,      0.7f },
        { 15.f,     0.1f,       0.6f },
        { 20.f,     0.1f,       0.5f },
        { 25.f,     0.1f,       0.3f }
    };

    for( i = 0; i < kNumWindDep; i++ )
        fWindDeps[i] = windDeps[i];

    ISetupTextureWaves();
}

void plWaveSet7::ISetupTextureWaves()
{
    // Fill in with sine waves.
    //  Start with 1/4 length of map = size / 4
    //  Go to 4 times length of pixel = 4
    //  Dir = U +- iLen/(numLen/2) * V
    //
    // For each mip level
    //      For each pixel
    //          For each wavelen
    //              Add height (sine) to alpha, normal (cosine) to color
    //          Scrunch
    //      For each wavelen
    //          // Half the wavelen to match higher mip level
    //          // If a wavelen falls below min (4), discard it and smaller lens
    //          len /= 2
    //          if( len < 4 )
    //          {
    //              numLen = iLen
    //              break;
    //          }
    //

    // Set up our table of amplitudes, wavelengths and wave directions

    // our rotation/scale in our texture transform will be
    // scale = waveKs.fK
    // dir.x * scale, dir.y * scale
    // -dir.y * scale, dir.x * scale
    // So to tile, dir.x * scale and dir.y * scale must be integers.
    // We select a randomized direction and a wavelength, then
    // round the scaled direction components to be integral, then
    // figure out what wavelength we actually came up with.
    int i;
    for( i = 0; i < kNumTexWaves; i++ )
    {
        IInitTexWave(i);
    }
    fTrialUpdate &= ~kReInitWaves;

    return;
}

void plWaveSet7::IInitTexWave(int i)
{
    hsScalar rads = fRand.RandMinusOneToOne() * TexState().fAngleDev;
    hsScalar dx = sin(rads);
    hsScalar dy = cos(rads);


    hsScalar tx = dx;
    dx = fWindDir.fY * dx - fWindDir.fX * dy;
    dy = fWindDir.fX * tx + fWindDir.fY * dy;

    hsScalar maxLen = TexState().fMaxLength * kCompositeSize / State().fRippleScale;
    hsScalar minLen = TexState().fMinLength * kCompositeSize / State().fRippleScale;
    hsScalar len = hsScalar(i) / hsScalar(kNumTexWaves-1) * (maxLen - minLen) + minLen;

    hsScalar reps = hsScalar(kCompositeSize) / len;

    dx *= reps;
    dy *= reps;
    dx = float(int(dx >= 0 ? dx + 0.5f : dx - 0.5f));
    dy = float(int(dy >= 0 ? dy + 0.5f : dy - 0.5f));

    fTexWaves[i].fRotScale00 = dx;
    fTexWaves[i].fRotScale01 = dy;

    hsScalar effK = hsFastMath::InvSqrt(dx*dx + dy*dy);
    fTexWaves[i].fLen = hsScalar(kCompositeSize) * effK;
    fTexWaves[i].fFreq = hsScalarPI * 2.f / fTexWaves[i].fLen;
    fTexWaves[i].fAmp = fTexWaves[i].fLen * TexState().fAmpOverLen;
    fTexWaves[i].fPhase = fRand.RandZeroToOne();
    
    fTexWaves[i].fDirX = dx * effK;
    fTexWaves[i].fDirY = dy * effK;
}

/////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////
void plWaveSet7::SetSceneNode(const plKey& key)
{
    fSceneNode = key;
}

void plWaveSet7::AddDynaDecalMgr(plKey& key)
{
    plGenRefMsg* msg = TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnRequest, 0, kRefDynaDecalMgr);
    hsgResMgr::ResMgr()->AddViaNotify(key, msg, plRefFlags::kPassiveRef);

    msg = TRACKED_NEW plGenRefMsg(key, plRefMsg::kOnRequest, 0, plDynaRippleVSMgr::kRefWaveSetBase);
    hsgResMgr::ResMgr()->AddViaNotify(GetKey(), msg, plRefFlags::kPassiveRef);
}

void plWaveSet7::RemoveDynaDecalMgr(plKey& key)
{
    plGenRefMsg* msg = TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnRemove, 0, kRefDynaDecalMgr);
    msg->SetRef(key->ObjectIsLoaded());
    msg->Send();

    msg = TRACKED_NEW plGenRefMsg(key, plRefMsg::kOnRemove, 0, plDynaRippleVSMgr::kRefWaveSetBase);
    msg->SetRef(this);
    msg->Send();
}

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

void plWaveSet7::IUpdateLayers(hsScalar dt)
{
    IUpdateBumpLayers(dt);

    ISubmitRenderRequests();
}

void plWaveSet7::IUpdateBumpLayers(hsScalar dt)
{
    plCONST(hsScalar) speedHack(1.f / 3.f);
    int i;
    for( i = 0; i < kNumTexWaves; i++ )
    {
        hsScalar speed = hsFastMath::InvSqrtAppr(fTexWaves[i].fLen / (2.f * hsScalarPI * kGravConst)) * speedHack;
        fTexWaves[i].fPhase -= dt * speed;
        fTexWaves[i].fPhase -= int(fTexWaves[i].fPhase);

        if( fBumpLayers[i] )
        {
            hsMatrix44 xfm = fBumpLayers[i]->GetTransform();

            xfm.fMap[0][0] = fTexWaves[i].fRotScale00;
            xfm.fMap[0][1] = fTexWaves[i].fRotScale01;
            xfm.fMap[1][0] = -fTexWaves[i].fRotScale01;
            xfm.fMap[1][1] = fTexWaves[i].fRotScale00;

            xfm.fMap[0][3] = fTexWaves[i].fPhase;
            fBumpLayers[i]->SetTransform(xfm);
        }

        LogF("%.4d - L%8.4f R(%5.3f, %5.3f)", i, fTexWaves[i].fLen, fTexWaves[i].fRotScale00, fTexWaves[i].fRotScale01);
        GraphLen(fTexWaves[i].fLen);
    }
}

void plWaveSet7::ISubmitRenderRequests()
{
    if( fBumpReqMsg )
        fBumpReqMsg->SendAndKeep();

    int i;
    for( i = 0; i < kGraphShorePasses; i++ )
    {
        if( fGraphReqMsg[i] )
            fGraphReqMsg[i]->SendAndKeep();
    }
}

plMipmap* plWaveSet7::ICreateBumpBitmapFFP(hsScalar amp, hsScalar dx, hsScalar dy) const
{
    return nil;
}

hsGMaterial* plWaveSet7::ICreateBumpLayersFFP()
{
    // Create kNumTexWaves bitmaps, each containing a single cycle of values:
    //      mag = 0.25 * -Amplitude * cos(i/sz*2*Pi)
    //      r = mag * dx * 0.5 + 0.5
    //      g = mag * dy * 0.5 + 0.5
    //      b = 0.7 * 0.25 * 0.5 + 0.5
    //      a = 1.f
    //
    //  Where:
    //      Amplitude == wave.amp * wave.freq * kNormHack(3.0)
    //      dx, dy == wave.fDir
    //

    // Create a blank material

    // Create base layer
    //      Blend mode is none
    //      Transform scales to wave[0].freq
    //          No rotation, translation
    //  Add bitmap 0.

    // For layers 1..kNumTexWaves
    //      Create blank layer
    //          Blend mode is ADDSIGNED
    //          Transform scales to wave[i].freq
    //          No rotation or translation.
    //      Add bitmap i;

    // NOTE: if we use the fixed function pipeline to composite
    //      these into the CompositeMaterial, then they all need to
    //      be scaled down to avoid saturation on summation.
    //      Doing the composite with a pixel shader would allow the
    //      textures to be stored in full 256 range, and then scaled
    //      down during the summation, after interpolation. Would
    //      that be better? Not sure. Pixel shader registers only have
    //      a range of [-1..1], and it's hinted to be 8-bit fixed point.
    //      So we'd still have to scale down texture value (losing precision),
    //      then add it in.
    //
    //      Also, for pixel shaded compositing, we can just store the cosine
    //      values in each layer, with no rotation in the bitmap values, just
    //      in the layer transform. No rotation would make for a cleaner
    //      interpolation (not interpolating diagonally across the waves).
    //      How much difference is that? Dunno. But it would also allow the
    //      bitmap to hold the full range [-1..1]=>[0..255] of cosine values,
    //      scaling them down with constants. The pixel shader would look like:
    //
    //          tex     t0;
    //          tex     t1;
    //          tex     t2;
    //          tex     t3;
    //
    //          mul     r0, t0_bx2, c0;
    //          mad     r0, t1_bx2, c1, r0;
    //          mad     r0, t2_bx2, c2, r0;
    //          mad     r0, t3_bx2, c3, r0;
    //          // Now bias it back into range [0..1] for output.
    //          mad     r0, r0, c4, c5;     // c4 = (0.5, 0.5, 0.5, 1), c5 = (0.5, 0.5, 0.5, 0)
    //          // or add_d2    r0, r0, c0.b; // except this is likely to saturate before the divide. Dunno.
    //
    //      where the c[i] constants would be:
    //          c[i] = -wave[i].amp * wave[i].freq * kSomeNormHack * (wave[i].fDir.x, wave[i].fDir.y, 0, 1)
    //      except c[0].b = 0.7;
    //      In fact, we could cut it down to 2 textures using alpha replicate, except we need different
    //      transforms for each (for animation). So screw that.
    //
    //      Note that the above won't run on a ps.1.0 pixel shader, because the combined number of tex and
    //      arith instructions is 9. Could make a version that was okay for ps.1.0 by doing:
    //
    //      mad     r0, t0_bias, c0, c4; // t0[-0.5..0.5]*c0 + 0.5, which is [0..1]
    //      mad     r0, t1_bias, c1, r0; // + t1[-0.5..0.5]*c1
    //      mad     r0, t2_bias, c2, r0; // + t2[-0.5..0.5]*c2
    //      mad     r0, t3_bias, c3, r0; // + t3[-0.5..0.5]*c3
    //
    //      which doesn't need a final bias instruction because it remains biased all the way through
    //      at the cost of 1 bit of precision.
    //      Whatever.
    //      

    // return material;
    return nil;
}

plMipmap* plWaveSet7::ICreateBiasNoiseMap()
{
    const int size = kCompositeSize >> 2;
    plMipmap* mipMap = TRACKED_NEW plMipmap(
        size, size,
        plMipmap::kARGB32Config,
        1, 
        plMipmap::kUncompressed,
        plMipmap::UncompressedInfo::kRGB8888);

    char buff[256];
    sprintf(buff, "%s_%s", GetKey()->GetName(), "BiasBitPS");
    hsgResMgr::ResMgr()->NewKey(buff, mipMap, GetKey()->GetUoid().GetLocation());

    int i;
    for( i = 0; i < size; i++ )
    {
        int j;
        for( j = 0; j < size; j++ )
        {
            hsScalar x = fRand.RandMinusOneToOne();
            hsScalar y = fRand.RandMinusOneToOne();

            UInt8 r = UInt8((x * 0.5f + 0.5f) * 255.999f);
            UInt8 g = UInt8((y * 0.5f + 0.5f) * 255.999f);

//          r = g = 0xff; // SATURATE

            UInt32* val = mipMap->GetAddr32(i, j);
            *val = (0xff << 24)
                | (r << 16)
                | (g << 8)
                | 0xff;
        }
    }
    // For bonus points, modulate by a VERY coherent noise function.
    return mipMap;
}

plMipmap* plWaveSet7::ICreateBumpMipmapPS()
{
//  const int sizeV = kCompositeSize;
    const int sizeV = 1;

    const int kNumLevels = 8; // must be log2(kCompositeSize)
    hsAssert(kCompositeSize == (1 << kNumLevels), "Mismatch on size and num mip levels");

    if( !fCosineLUT )
    {
        plMipmap* mipMap = TRACKED_NEW plMipmap(
        kCompositeSize, sizeV,
        plMipmap::kARGB32Config,
        kNumLevels, 
        plMipmap::kUncompressed,
        plMipmap::UncompressedInfo::kRGB8888);

        char buff[256];
        sprintf(buff, "%s_%s", GetKey()->GetName(), "BumpBitPS");
        hsgResMgr::ResMgr()->NewKey(buff, mipMap, GetKey()->GetUoid().GetLocation());

        hsgResMgr::ResMgr()->SendRef(mipMap->GetKey(), TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnRequest, 0, kRefCosineLUT), plRefFlags::kActiveRef);
    }
    hsAssert(fCosineLUT, "Failed to make cosine lookup table");

    int k;
    for( k = 0; k < kNumLevels; k++ )
    {
        fCosineLUT->SetCurrLevel(k);
        
        const int sizeU = kCompositeSize >> k;

        int i;
        for( i = 0; i < sizeU; i++ )
        {
            hsScalar y = hsScalar(i);
            hsScalar dist = hsScalar(i) / hsScalar(sizeU-1) * 2.f * hsScalarPI;
            hsScalar c = cos(dist);
            hsScalar s = sin(dist);
            s *= 0.5f;
            s += 0.5f;
            s = hsScalar(pow(s, TexState().fChop));
            c *= s;
            UInt8 cosDist = UInt8((c * 0.5 + 0.5) * 255.999f);
            int j;
            for( j = 0; j < sizeV; j++ )
            {
                UInt32* val = fCosineLUT->GetAddr32(i, j);
                *val = (0xff << 24)
                    | (cosDist << 16)
                    | (cosDist << 8)
                    | 0xff;
            }
        }
    }
    fCosineLUT->MakeDirty();

    return fCosineLUT;
}

void plWaveSet7::IAddBumpBiasLayer(hsGMaterial* mat)
{
    if( !fBiasLayer[0] )
    {
        plMipmap* mipMap = ICreateBiasNoiseMap();

        int i;
        for( i = 0; i < 2; i++ )
        {
            plLayer* layer = TRACKED_NEW plLayer;
            char buff[256];
            sprintf(buff, "%s_%s_%d", GetKey()->GetName(), "Bias", i);
            hsgResMgr::ResMgr()->NewKey(buff, layer, GetKey()->GetUoid().GetLocation());

            layer->SetBlendFlags(hsGMatState::kBlendAdd);
            layer->SetZFlags(hsGMatState::kZNoZRead | hsGMatState::kZNoZWrite);
            layer->SetShadeFlags(hsGMatState::kShadeReallyNoFog
                | hsGMatState::kShadeNoProjectors
                | hsGMatState::kShadeNoShade);
            layer->SetClampFlags(0);
            layer->SetMiscFlags(0);
            layer->SetMiscFlags(i ? 0 : hsGMatState::kMiscRestartPassHere);

            layer->SetAmbientColor(hsColorRGBA().Set(0.25f, 0.25f, 0.f, 0.f));
            layer->SetRuntimeColor(hsColorRGBA().Set(0.f, 0.f, 0.f, 0.f));
            layer->SetOpacity(0.f);

            layer->SetUVWSrc(0);

            plLayRefMsg* refMsg = TRACKED_NEW plLayRefMsg(layer->GetKey(), plRefMsg::kOnCreate, 0, plLayRefMsg::kTexture);
            hsgResMgr::ResMgr()->SendRef(mipMap->GetKey(), refMsg, plRefFlags::kActiveRef);

            IAddBumpBiasShaders(layer);

            fBiasLayer[i] = layer; // SHOULD BE REFFED (SO SHOULD fBumpLayers)
        }
    }

    mat->AddLayerViaNotify(fBiasLayer[0]);
    mat->AddLayerViaNotify(fBiasLayer[1]);

}

plLayer* plWaveSet7::ICreateBumpLayerPS(plMipmap* mipMap, hsGMaterial* bumpMat, int which)
{
    plLayer* layer = TRACKED_NEW plLayer;
    char buff[256];
    sprintf(buff, "%s_%s_%d", GetKey()->GetName(), "BumpLayerPS", which);
    hsgResMgr::ResMgr()->NewKey(buff, layer, GetKey()->GetUoid().GetLocation());

    layer->SetBlendFlags(which ? hsGMatState::kBlendAdd : 0);
    layer->SetZFlags(hsGMatState::kZNoZRead | hsGMatState::kZNoZWrite);
    layer->SetShadeFlags(hsGMatState::kShadeReallyNoFog
        | hsGMatState::kShadeNoProjectors
        | hsGMatState::kShadeNoShade
        | hsGMatState::kShadeWhite);
    layer->SetClampFlags(0);
    layer->SetMiscFlags(0);
    if( (which / kBumpPerPass) * kBumpPerPass == which )
        layer->SetMiscFlags(hsGMatState::kMiscRestartPassHere);

    layer->SetAmbientColor(hsColorRGBA().Set(0.f, 0.f, 0.f, 1.f));
    layer->SetRuntimeColor(hsColorRGBA().Set(1.f, 1.f, 1.f, 1.f));
    layer->SetOpacity(1.f);

    layer->SetUVWSrc(0);

    // Set up the transform.
    hsMatrix44 xfm;
    xfm.Reset();
    xfm.NotIdentity();

    xfm.fMap[0][0] = fTexWaves[which].fRotScale00;
    xfm.fMap[0][1] = fTexWaves[which].fRotScale01;
    xfm.fMap[1][0] = -fTexWaves[which].fRotScale01;
    xfm.fMap[1][1] = fTexWaves[which].fRotScale00;
    layer->SetTransform(xfm);

    bumpMat->AddLayerViaNotify(layer);

    plLayRefMsg* refMsg = TRACKED_NEW plLayRefMsg(layer->GetKey(), plRefMsg::kOnCreate, 0, plLayRefMsg::kTexture);
    hsgResMgr::ResMgr()->SendRef(mipMap->GetKey(), refMsg, plRefFlags::kActiveRef);

    return layer;
}

hsGMaterial* plWaveSet7::ICreateBumpLayersPS()
{
    if( fBumpMat )
        return fBumpMat;

    // Create four bitmaps, each containing a single cycle of values:
    //      mag = cos(i/sz*2*Pi)
    //      r = 0
    //      g = mag* 0.5 + 0.5
    //      b = 1.f
    //      a = 1.f
    //
    //  Where:
    //      Amplitude == wave.amp * wave.freq * kNormHack(3.0)
    //

    // Create a blank material
    hsGMaterial* bumpMat = TRACKED_NEW hsGMaterial;
    char buff[256];
    sprintf(buff, "%s_%s", GetKey()->GetName(), "BumpMatPS");
    hsgResMgr::ResMgr()->NewKey(buff, bumpMat, GetKey()->GetUoid().GetLocation());

    plMipmap* mipMap = ICreateBumpMipmapPS();

    // Create base layer
    //      Blend mode is none
    //      Transform scales to wave[0].freq
    //          No translation
    //          Rotation is by wave[0].fDir
    //  Add bitmap 0.
    int i;
    for( i = 0; i < kNumBumpShaders; i++ )
    {
        int nBegin = bumpMat->GetNumLayers();

        int j;
        for( j = 0; j < kBumpPerPass; j++ )
            fBumpLayers[i*kBumpPerPass + j] = ICreateBumpLayerPS(mipMap, bumpMat, i*kBumpPerPass + j);

        IAddBumpVertexShader(bumpMat, i, nBegin, nBegin + kBumpPerPass-1);
        IAddBumpPixelShader(bumpMat, i, nBegin, nBegin + kBumpPerPass-1);

    }
    IAddBumpBiasLayer(bumpMat);


    // Need to add this via notify to ourselves.
    hsgResMgr::ResMgr()->SendRef(bumpMat->GetKey(), TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnRequest, 0, kRefBumpMat), plRefFlags::kActiveRef);

    ICreateBumpDrawable();

    return fBumpMat = bumpMat;
}

void plWaveSet7::IAddBumpBiasShaders(plLayer* layer)
{
    if( !fBiasVShader )
    {
        plShader* vShader = TRACKED_NEW plShader;

        char buff[256];
        sprintf(buff, "%s_BiasVS", GetKey()->GetName());
        hsgResMgr::ResMgr()->NewKey(buff, vShader, GetKey()->GetUoid().GetLocation());
        vShader->SetIsPixelShader(false);
        
        vShader->SetNumConsts(plBiasVS::kNumConsts);

        vShader->SetVector(plBiasVS::kTexU0,
            1.f,
            0.f,
            0.f,
            0.f);
        vShader->SetVector(plBiasVS::kTexV0,
            0.f,
            1.f,
            0.f,
            0.f);

        vShader->SetVector(plBiasVS::kTexU1,
            1.f,
            0.f,
            0.f,
            0.f);
        vShader->SetVector(plBiasVS::kTexV1,
            0.f,
            1.f,
            0.f,
            0.f);

        vShader->SetVector(plBiasVS::kNumbers,
            0.f,
            0.5f,
            1.f,
            2.f);

        hsVector3 specVec = State().fSpecVec;
        hsScalar biasScale = 0.5f * specVec[State().kNoise] / (hsScalar(kNumBumpShaders) + specVec[State().kNoise]);
        vShader->SetVector(plBiasVS::kScaleBias,
            biasScale,
            biasScale,
            0.f,
            1.f);

        vShader->SetInputFormat(1);
        vShader->SetOutputFormat(0);

//      static const plShaderDecl vDecl("sha/vs_BiasNormals.inl");
//      vShader->SetDecl(&vDecl);
        vShader->SetDecl(plShaderTable::Decl(vs_BiasNormals));

        hsgResMgr::ResMgr()->SendRef(vShader->GetKey(), TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnRequest, 0, kRefBiasVShader), plRefFlags::kActiveRef);


        fBiasVShader = vShader;
    }

    plLayRefMsg* refMsg = TRACKED_NEW plLayRefMsg(layer->GetKey(), plRefMsg::kOnCreate, 0, plLayRefMsg::kVertexShader); 
    hsgResMgr::ResMgr()->SendRef(fBiasVShader->GetKey(), refMsg, plRefFlags::kActiveRef);


    if( !fBiasPShader )
    {
        plShader* pShader = TRACKED_NEW plShader;

        char buff[256];
        sprintf(buff, "%s_BiasPS", GetKey()->GetName());
        hsgResMgr::ResMgr()->NewKey(buff, pShader, GetKey()->GetUoid().GetLocation());
        pShader->SetIsPixelShader(true);
        
        pShader->SetNumConsts(plBiasPS::kNumConsts);

        pShader->SetInputFormat(0);
        pShader->SetOutputFormat(0);

//      static const plShaderDecl pDecl("sha/ps_BiasNormals.inl");
//      pShader->SetDecl(&pDecl);
        pShader->SetDecl(plShaderTable::Decl(ps_BiasNormals));

        hsgResMgr::ResMgr()->SendRef(pShader->GetKey(), TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnRequest, 0, kRefBiasPShader), plRefFlags::kActiveRef);

        fBiasPShader = pShader;
    }

    refMsg = TRACKED_NEW plLayRefMsg(layer->GetKey(), plRefMsg::kOnCreate, 0, plLayRefMsg::kPixelShader);   
    hsgResMgr::ResMgr()->SendRef(fBiasPShader->GetKey(), refMsg, plRefFlags::kActiveRef);

}

void plWaveSet7::IAddBumpVertexShader(hsGMaterial* mat, int iShader, int iFirst, int iLast)
{
    if( !fBumpVShader[0] )
    {
        int iBase = 0;
        while( iBase < kNumTexWaves )
        {
            int iShader = iBase / kBumpPerPass;

            plShader* vShader = TRACKED_NEW plShader;
            char buff[256];
            sprintf(buff, "%s_BumpVS_%d", GetKey()->GetName(), iShader);
            hsgResMgr::ResMgr()->NewKey(buff, vShader, GetKey()->GetUoid().GetLocation());
            vShader->SetIsPixelShader(false);
            
            vShader->SetNumConsts(plBumpVS::kNumConsts);

            vShader->SetVector(plBumpVS::kNumbers,
                0.f,
                0.5f,
                1.f,
                2.f);

            vShader->SetInputFormat(1);
            vShader->SetOutputFormat(0);

            vShader->SetNumPipeConsts(kBumpPerPass);
            int i;
            for( i = 0; i < kBumpPerPass; i++ )
            {
                vShader->SetPipeConst(i, static_cast<plPipeConst::Type>(plPipeConst::kTex1x4_0 + i), plBumpVS::kUXform0 + i);
            }

            vShader->SetDecl(plShaderTable::Decl(vs_CompCosines));

            hsgResMgr::ResMgr()->SendRef(vShader->GetKey(), TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnRequest, iShader, kRefBumpVShader), plRefFlags::kActiveRef);

            fBumpVShader[iShader] = vShader;

            iBase += kBumpPerPass;
        }
    }

    IAddShaderToLayers(mat, iFirst, iLast, plLayRefMsg::kVertexShader, fBumpVShader[iShader]);
}

void plWaveSet7::IAddBumpPixelShader(hsGMaterial* mat, int iShader, int iFirst, int iLast)
{
    if( !fBumpPShader[0] )
    {
        int iBase = 0;
        while( iBase < kNumTexWaves )
        {
            int iShader = iBase / kBumpPerPass;

            plShader* pShader = TRACKED_NEW plShader;
            char buff[256];
            sprintf(buff, "%s_BumpPS_%d", GetKey()->GetName(), iShader);
            hsgResMgr::ResMgr()->NewKey(buff, pShader, GetKey()->GetUoid().GetLocation());
            pShader->SetIsPixelShader(true);
            
            pShader->SetNumConsts(plBumpPS::kNumConsts);

            int iLay;
            for( iLay = 0; iLay < kBumpPerPass; iLay++ )
            {
                pShader->SetVector(plBumpPS::kWave0,
                    -fTexWaves[iBase + iLay].fDirX * (1.f / hsScalar(kBumpPerPass)),
                    -fTexWaves[iBase + iLay].fDirY * (1.f / hsScalar(kBumpPerPass)),
                    1.f,
                    1.f);
            }

            pShader->SetInputFormat(0);
            pShader->SetOutputFormat(0);

        //  pShader->SetShaderFileName("sha/ps_CompCosines.inl");
        //  pShader->SetShaderFileName("sha/ps_TestPos.inl");
//          static const plShaderDecl moreDecl("sha/ps_MoreCosines.inl");
//          pShader->SetDecl(&moreDecl);

            pShader->SetDecl(plShaderTable::Decl(ps_MoreCosines));

            pShader->SetVector(plBumpPS::kHalfOne, 0.25f, 0.25f, 0.25f, 1.f);

            hsgResMgr::ResMgr()->SendRef(pShader->GetKey(), TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnRequest, iShader, kRefBumpPShader), plRefFlags::kActiveRef);

            fBumpPShader[iShader] = pShader;

            iBase += kBumpPerPass;
        }
    }

    IAddShaderToLayers(mat, iFirst, iLast, plLayRefMsg::kPixelShader, fBumpPShader[iShader]);
}

plDrawableSpans* plWaveSet7::ICreateBumpDrawable()
{
    fBumpDraw = TRACKED_NEW plDrawableSpans;
    char buff[256];
    sprintf(buff, "%s_BumpDraw", GetKey()->GetName());
    hsgResMgr::ResMgr()->NewKey(buff, fBumpDraw, GetKey()->GetUoid().GetLocation());

    ICreateClearDrawable(fBumpDraw, fBumpMat);
    
    hsgResMgr::ResMgr()->SendRef(fBumpDraw->GetKey(), TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnRequest, 0, kRefBumpDraw), plRefFlags::kActiveRef);

    return fBumpDraw;
}

plDrawableSpans* plWaveSet7::ICreateClearDrawable(plDrawableSpans* drawable, hsGMaterial* mat)
{
//  drawable->SetNativeProperty(plDrawable::kPropVolatile, true);

    hsPoint3        pos[4];
    hsVector3       norm[4];
    hsPoint3        uvw[4];

    pos[0].fX = -1.f;
    pos[0].fY = -1.f;
    pos[0].fZ = 0.5f;

    norm[0].Set(0.f, 0.f, -1.f);

    uvw[0].fX = 0.5f / kCompositeSize;
    uvw[0].fY = 0.5f / kCompositeSize;
    uvw[0].fZ = 0;

    // P1
    pos[1] = pos[0];
    pos[1].fY += 2.f;

    norm[1] = norm[0];

    uvw[1] = uvw[0];
    uvw[1].fY += 1.f;

    // P2
    pos[2] = pos[0];
    pos[2].fX += 2.f;
    pos[2].fY += 2.f;

    norm[2] = norm[0];

    uvw[2] = uvw[0];
    uvw[2].fX += 1.f;
    uvw[2].fY += 1.f;
    
    // P3
    pos[3] = pos[0];
    pos[3].fX += 2.f;

    norm[3] = norm[0];

    uvw[3] = uvw[0];
    uvw[3].fX += 1.f;

    UInt16 idx[6];
    idx[0] = 1;
    idx[1] = 0;
    idx[2] = 2;
    
    idx[3] = 2;
    idx[4] = 0;
    idx[5] = 3;


    plDrawableGenerator::GenerateDrawable( 4, pos, norm, 
                                                        uvw, 1,
                                                        nil, false, nil,
                                                        6, idx, 
                                                        mat, 
                                                        hsMatrix44::IdentityMatrix(), 
                                                        false,
                                                        nil,
                                                        drawable);

    return drawable;
}

plRenderRequest* plWaveSet7::ICreateRenderRequest(plRenderTarget* rt, plDrawableSpans* draw, hsScalar pri)
{
    plRenderRequest* req = TRACKED_NEW plRenderRequest;

    static plPageTreeMgr emptyMgr;
    req->SetPageTreeMgr(&emptyMgr);

    req->SetClearDrawable(draw);

    req->SetOrthogonal(true);

    req->SetLocalTransform(hsMatrix44::IdentityMatrix(), hsMatrix44::IdentityMatrix());
    req->SetCameraTransform(hsMatrix44::IdentityMatrix(), hsMatrix44::IdentityMatrix());

    req->SetHither(0);
    req->SetYon(2.f);
    req->SetSizeX(2.f);
    req->SetSizeY(2.f);
    
    req->SetPriority(pri);

    req->SetRenderTarget(rt);

    return req;
}

plRenderTarget* plWaveSet7::ICreateTransferRenderTarget(const char* name, int size)
{
    UInt16 flags = plRenderTarget::kIsTexture | plRenderTarget::kIsOrtho;
    UInt8 bitDepth = 32;
    UInt8 zDepth = 0;
    UInt8 stencilDepth = 0;
    
    plRenderTarget* rt = TRACKED_NEW plRenderTarget(flags, size, size, bitDepth, zDepth, stencilDepth);

    char buff[256];
    sprintf(buff, "%s_%s", GetKey()->GetName(), name);
    hsgResMgr::ResMgr()->NewKey(buff, rt, GetKey()->GetUoid().GetLocation());

    return rt;
}

plLayer* plWaveSet7::ICreateTotalLayer(plBitmap* bm, hsGMaterial* mat, int which, const char* suff)
{
    plLayer* layer = mat->GetNumLayers() > which ? plLayer::ConvertNoRef(mat->GetLayer(which)->BottomOfStack()) : nil;
    if( !layer )
    {
        layer = TRACKED_NEW plLayer;
        char buff[256];
        sprintf(buff, "%s_%sLayerPS_%d", GetKey()->GetName(), suff, which);
        hsgResMgr::ResMgr()->NewKey(buff, layer, GetKey()->GetUoid().GetLocation());

        layer->SetAmbientColor(hsColorRGBA().Set(0.f, 0.f, 0.f, 1.f));
        layer->SetRuntimeColor(State().fWaterTint);
        layer->SetOpacity(1.f);

        mat->AddLayerViaNotify(layer);
    }

    layer->SetBlendFlags(which ? hsGMatState::kBlendAddSigned : hsGMatState::kBlendAlpha);
    layer->SetZFlags(which ? hsGMatState::kZNoZWrite : 0);
    layer->SetShadeFlags(hsGMatState::kShadeNoProjectors
        | hsGMatState::kShadeNoShade);
    layer->SetClampFlags(0);
    layer->SetMiscFlags(hsGMatState::kMiscTwoSided);

    layer->SetUVWSrc(which);

    // Set up the transform.
    hsMatrix44 xfm;
    xfm.Reset();

    layer->SetTransform(xfm);

    plLayRefMsg* refMsg = TRACKED_NEW plLayRefMsg(layer->GetKey(), plRefMsg::kOnCreate, 0, plLayRefMsg::kTexture);
    hsgResMgr::ResMgr()->SendRef(bm->GetKey(), refMsg, plRefFlags::kActiveRef);

    return layer;
}

plLayer* plWaveSet7::ICreateTotalEnvLayer(plBitmap* envMap, hsGMaterial* mat, int which, const char* pref)
{
    plLayer* layer = TRACKED_NEW plLayer;
    char buff[256];
    sprintf(buff, "%s_%s_%s_%d", GetKey()->GetName(), pref, "EnvLayerPS", which);
    hsgResMgr::ResMgr()->NewKey(buff, layer, GetKey()->GetUoid().GetLocation());

    layer->SetBlendFlags(which ? hsGMatState::kBlendAddSigned : 0);
    layer->SetZFlags(which ? hsGMatState::kZNoZWrite : 0);
    layer->SetShadeFlags(hsGMatState::kShadeNoProjectors
        | hsGMatState::kShadeNoShade);
    layer->SetClampFlags(0);
    layer->SetMiscFlags(0);

    layer->SetAmbientColor(hsColorRGBA().Set(0.f, 0.f, 0.f, 1.f));
    layer->SetRuntimeColor(hsColorRGBA().Set(1.f, 1.f, 1.f, 1.f));
    layer->SetOpacity(1.f);

    layer->SetUVWSrc(which);

    // Set up the transform.
    hsMatrix44 xfm;
    xfm.Reset();

    layer->SetTransform(xfm);

    plLayRefMsg* refMsg = TRACKED_NEW plLayRefMsg(layer->GetKey(), plRefMsg::kOnCreate, 0, plLayRefMsg::kTexture);
    hsgResMgr::ResMgr()->SendRef(envMap->GetKey(), refMsg, plRefFlags::kActiveRef);

    mat->AddLayerViaNotify(layer);

    return layer;
}

hsGMaterial* plWaveSet7::ICreateFixedMatPS(hsGMaterial* mat, const int numUVWs)
{
    hsAssert(mat, "Nil mat should have been filtered out before calling this");

    // Check if we've already done this one.
    if( (mat->GetNumLayers() == 4)
        &&(mat->GetLayer(3) == fFixedLayers[3]) )
        return mat;

    // First, strip off whatever's on there now.
    // If this is the 
    int i;
    for( i = mat->GetNumLayers()-1; i > 0; i-- )
    {
        plMatRefMsg* refMsg = TRACKED_NEW plMatRefMsg(mat->GetKey(), plRefMsg::kOnRemove, i, plMatRefMsg::kLayer);
        hsgResMgr::ResMgr()->SendRef(mat->GetLayer(i)->GetKey(), refMsg, plRefFlags::kActiveRef);
    }

    plRenderTarget* rt = ICreateTransferRenderTarget("CompRT", kCompositeSize);

    if( mat->GetNumLayers() && mat->GetLayer(0) )
    {
        fState.fWaterTint = mat->GetLayer(0)->GetRuntimeColor();
    }

    fFixedLayers[0] = ICreateTotalLayer(rt, mat, 0, "Fix");
    fFixedLayers[1] = ICreateTotalLayer(rt, mat, 1, "Fix");
    fFixedLayers[2] = ICreateTotalLayer(rt, mat, 2, "Fix");
    fFixedLayers[3] = ICreateTotalEnvLayer(fEnvMap, mat, 3, "Fix");


    fBumpReq = ICreateRenderRequest(rt, fBumpDraw, -100.f);
    fBumpReqMsg = TRACKED_NEW plRenderRequestMsg(GetKey(), fBumpReq);

    IAddFixedVertexShader(mat, numUVWs);
    IAddFixedPixelShader(mat);
    
    return mat;
}

void plWaveSet7::ICreateFixedMat(hsGMaterial* mat, const int numUVWs)
{
    if( !mat )
        return;

    if( !fEnvMap )
    {
        plDynamicEnvMap* env = TRACKED_NEW plDynamicEnvMap((UInt16)fEnvSize, (UInt16)fEnvSize, 32);
        hsgResMgr::ResMgr()->NewKey(GetKey()->GetName(), env, GetKey()->GetUoid().GetLocation());
        fEnvMap = env;
        env->SetPosition(hsPoint3(0, 0, 50.f));
        env->SetPosition(State().fEnvCenter);
        env->SetYon(10000.f);
        env->SetRefreshRate(State().fEnvRefresh);
        env->Init();
        env->ReRender();
    }

    ICreateBumpLayersPS();
    ICreateFixedMatPS(mat, numUVWs);
}


void plWaveSet7::IAddShoreVertexShader(hsGMaterial* mat)
{
    if( !fShoreVShader )
    {

        plShader* vShader = TRACKED_NEW plShader;

        char buff[256];
        sprintf(buff, "%s_ShoreVS", GetKey()->GetName());
        hsgResMgr::ResMgr()->NewKey(buff, vShader, GetKey()->GetUoid().GetLocation());
        vShader->SetIsPixelShader(false);
        
        vShader->SetNumConsts(plShoreVS::kNumConsts);

        vShader->SetInputFormat(1); // This should really be one!!!
        vShader->SetOutputFormat(0);

        vShader->SetVector(plShoreVS::kSinConsts, 1.f, -1.f/6.f, 1.f/120.f, -1.f/5040.f);
        vShader->SetVector(plShoreVS::kCosConsts, 1.f, -1.f/2.f, 1.f/24.f, -1.f/720.f);
        vShader->SetVector(plShoreVS::kPiConsts, 1.f / (8.f*hsScalarPI*4.f*4.f), hsScalarPI/2.f, hsScalarPI, hsScalarPI*2.f);
        vShader->SetVector(plShoreVS::kNumericConsts, 0, 0.5f, 1.f, 2.f);

        plConst(hsScalar) kK1(0.5f);
        plConst(hsScalar) kK2(1.5f);
        hsScalar negK1OverK2Sq = -kK1 / (kK2 * kK2);
        vShader->SetVector(plShoreVS::kIncline, negK1OverK2Sq, kK1, 0.f, 0.f);

        vShader->SetNumPipeConsts(5);
        vShader->SetPipeConst(0, plPipeConst::kWorldToNDC, plShoreVS::kWorldToNDC);
        vShader->SetPipeConst(1, plPipeConst::kTex3x4_0, plShoreVS::kTex0Transform);
        vShader->SetPipeConst(2, plPipeConst::kLocalToWorld, plShoreVS::kLocalToWorld);
        vShader->SetPipeConst(3, plPipeConst::kLayRuntime, plShoreVS::kShoreTint);
        vShader->SetPipeConst(4, plPipeConst::kFogSet, plShoreVS::kFogSet);

//      vShader->SetShaderFileName("sha/vs_Shore.inl");
//      vShader->SetShaderFileName("sha/vs_ShoreSteep.inl");
//      vShader->SetShaderFileName("sha/vs_ShoreSucks.inl");
//      vShader->SetShaderFileName("sha/vs_ShoreLeave.inl");
//      vShader->SetShaderFileName("sha/vs_ShoreLeave6.inl");
//      vShader->SetDecl(plShaderTable::Decl(vs_ShoreLeave6));

//      static const plShaderDecl decl("sha/vs_ShoreLeave7.inl");
//      vShader->SetDecl(&decl);
        vShader->SetDecl(plShaderTable::Decl(vs_ShoreLeave7));

        hsgResMgr::ResMgr()->SendRef(vShader->GetKey(), TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnRequest, 0, kRefShoreVShader), plRefFlags::kActiveRef);

        fShoreVShader = vShader;

    }

    IAddShaderToLayers(mat, 0, mat->GetNumLayers()-1, plLayRefMsg::kVertexShader, fShoreVShader);

}

void plWaveSet7::IAddShorePixelShader(hsGMaterial* mat)
{
    if( !fShorePShader )
    {
        plShader* pShader = TRACKED_NEW plShader;

        char buff[256];
        sprintf(buff, "%s_ShorePS", GetKey()->GetName());
        hsgResMgr::ResMgr()->NewKey(buff, pShader, GetKey()->GetUoid().GetLocation());
        pShader->SetIsPixelShader(true);

//      pShader->SetShaderFileName("sha/ps_ShoreSucks.inl");
//      pShader->SetShaderFileName("sha/ps_ShoreLeave6.inl");
        pShader->SetDecl(plShaderTable::Decl(ps_ShoreLeave6));

        hsgResMgr::ResMgr()->SendRef(pShader->GetKey(), TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnRequest, 0, kRefShorePShader), plRefFlags::kActiveRef);

        fShorePShader = pShader;
    }

    IAddShaderToLayers(mat, 0, mat->GetNumLayers()-1, plLayRefMsg::kPixelShader, fShorePShader);
}

void plWaveSet7::IAddFixedVertexShader(hsGMaterial* mat, const int numUVWs)
{
    if( !fFixedVShader )
    {

        plShader* vShader = TRACKED_NEW plShader;

        char buff[256];
        sprintf(buff, "%s_FixedVS", GetKey()->GetName());
        hsgResMgr::ResMgr()->NewKey(buff, vShader, GetKey()->GetUoid().GetLocation());
        vShader->SetIsPixelShader(false);
        
        vShader->SetNumConsts(plFixedVS7::kNumConsts);

        vShader->SetInputFormat(numUVWs);
        vShader->SetOutputFormat(0);

        vShader->SetVector(plFixedVS7::kSinConsts, 1.f, -1.f/6.f, 1.f/120.f, -1.f/5040.f);
        vShader->SetVector(plFixedVS7::kCosConsts, 1.f, -1.f/2.f, 1.f/24.f, -1.f/720.f);
        vShader->SetVector(plFixedVS7::kPiConsts, 1.f / (8.f*hsScalarPI*4.f*4.f), hsScalarPI/2.f, hsScalarPI, hsScalarPI*2.f);
        vShader->SetVector(plFixedVS7::kNumericConsts, 0, 0.5f, 1.f, 2.f);

        vShader->SetNumPipeConsts(5);
        vShader->SetPipeConst(0, plPipeConst::kWorldToNDC, plFixedVS7::kWorldToNDC);
        vShader->SetPipeConst(1, plPipeConst::kCamPosWorld, plFixedVS7::kCameraPos);
        vShader->SetPipeConst(2, plPipeConst::kLocalToWorld, plFixedVS7::kLocalToWorld);
        vShader->SetPipeConst(3, plPipeConst::kLayRuntime, plFixedVS7::kWaterTint);
        vShader->SetPipeConst(4, plPipeConst::kFogSet, plFixedVS7::kFogSet);


//      static const plShaderDecl decl("sha/vs_WaveFixedFin7.inl");
//      vShader->SetDecl(&decl);
        vShader->SetDecl(plShaderTable::Decl(vs_WaveFixedFin7));


//      vShader->SetShaderFileName("sha/vs_WaveFixedFin6.inl");
//      vShader->SetShaderFileName("sha/vs_WaveFixedFin.inl");
//      vShader->SetShaderFileName("sha/vs_TestPos.inl");

        hsgResMgr::ResMgr()->SendRef(vShader->GetKey(), TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnRequest, 0, kRefFixedVShader), plRefFlags::kActiveRef);

        fFixedVShader = vShader;

    }

    IAddShaderToLayers(mat, 0, mat->GetNumLayers()-1, plLayRefMsg::kVertexShader, fFixedVShader);

}

// type is either plLayRefMsg::kVertexShader or plLayRefMsg::kPixelShader.
void plWaveSet7::IAddShaderToLayers(hsGMaterial* mat, int iFirst, int iLast, UInt8 type, plShader* shader)
{
    if( iFirst < 0 )
        iFirst = 0;
    if( UInt32(iLast) >= mat->GetNumLayers() )
        iLast = mat->GetNumLayers()-1;
    int i;
    for( i = iFirst; i <= iLast; i++ )
    {
        plLayer* layer = plLayer::ConvertNoRef(mat->GetLayer(i)->BottomOfStack());
        if( layer 
            && (layer->GetVertexShader() != shader) 
            && (layer->GetPixelShader() != shader) )
        {
            plLayRefMsg* refMsg = TRACKED_NEW plLayRefMsg(layer->GetKey(), plRefMsg::kOnCreate, 0, type);   
            hsgResMgr::ResMgr()->SendRef(shader->GetKey(), refMsg, plRefFlags::kActiveRef);

//          layer->SetShadeFlags(layer->GetShadeFlags() | hsGMatState::kShadeReallyNoFog);
        }
    }
}

void plWaveSet7::IAddFixedPixelShader(hsGMaterial* mat)
{
    if( !fFixedPShader )
    {
        plShader* pShader = TRACKED_NEW plShader;
        char buff[256];
        sprintf(buff, "%s_FixedPS", GetKey()->GetName());
        hsgResMgr::ResMgr()->NewKey(buff, pShader, GetKey()->GetUoid().GetLocation());
        pShader->SetIsPixelShader(true);
        
//      pShader->SetNumConsts(plFixedPS::kNumConsts);
        pShader->SetNumConsts(0);
        
        pShader->SetInputFormat(0);
        pShader->SetOutputFormat(0);
        
//      pShader->SetShaderFileName("sha/ps_WaveFixed.inl");
//      pShader->SetShaderFileName("sha/ps_TestPos.inl");
        pShader->SetDecl(plShaderTable::Decl(ps_WaveFixed));
        
        hsgResMgr::ResMgr()->SendRef(pShader->GetKey(), TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnRequest, 0, kRefFixedPShader), plRefFlags::kActiveRef);
        
        fFixedPShader = pShader;
    }
    
    IAddShaderToLayers(mat, 0, mat->GetNumLayers()-1, plLayRefMsg::kPixelShader, fFixedPShader);

}

void plWaveSet7::IAddRipVertexShader(hsGMaterial* mat, const plRipVSConsts& ripConsts)
{
    if( !fRipVShader )
    {
        plShader* vShader = TRACKED_NEW plShader;
        char buff[256];
        sprintf(buff, "%s_RipVS", GetKey()->GetName());
        hsgResMgr::ResMgr()->NewKey(buff, vShader, GetKey()->GetUoid().GetLocation());
        vShader->SetIsPixelShader(false);
        
        vShader->SetInputFormat(1); // This should really be one!!!
        vShader->SetOutputFormat(0);

        vShader->SetNumConsts(plRipVS::kNumConsts);

        vShader->SetVector(plRipVS::kSinConsts, 1.f, -1.f/6.f, 1.f/120.f, -1.f/5040.f);
        vShader->SetVector(plRipVS::kCosConsts, 1.f, -1.f/2.f, 1.f/24.f, -1.f/720.f);
        vShader->SetVector(plRipVS::kPiConsts, 1.f / (8.f*hsScalarPI*4.f*4.f), hsScalarPI/2.f, hsScalarPI, hsScalarPI*2.f);
        vShader->SetVector(plRipVS::kNumericConsts, 0, 0.5f, 1.f, 2.f);

        hsVector3 waterOffset = State().fWaterOffset;
        vShader->SetVector(plRipVS::kWaterLevel, 
            State().fWaterHeight + waterOffset.fX, 
            State().fWaterHeight + waterOffset.fY, 
            State().fWaterHeight + waterOffset.fZ, 
            State().fWaterHeight);

        hsVector3 maxAtten = State().fMaxAtten;
        hsVector3 minAtten = State().fMinAtten;
        hsVector3 depthFalloff = State().fDepthFalloff;
        vShader->SetVector(plRipVS::kDepthFalloff,
            (maxAtten.fX - minAtten.fX) / depthFalloff.fX,
            (maxAtten.fY - minAtten.fY) / depthFalloff.fY,
            (maxAtten.fZ - minAtten.fZ) / depthFalloff.fZ,
            1.f
            );
        vShader->SetVector(plRipVS::kMinAtten,
            minAtten.fX,
            minAtten.fY,
            minAtten.fZ,
            0.f
            );


        // Set up the ones passed in from the dynarippleVSmgr.
        vShader->SetVector(plRipVS::kTexConsts,
            ripConsts.fC1U,
            ripConsts.fC2U,
            ripConsts.fC1V,
            ripConsts.fC2V);

        vShader->SetVector(plRipVS::kLifeConsts,
            ripConsts.fInitAtten,
            0, // current time, we'll fill that in later
            ripConsts.fLife,
            1.f / (ripConsts.fLife - ripConsts.fDecay));

        plConst(hsScalar) kRipBias(0.1);
        vShader->SetVector(plRipVS::kRampBias,
            ripConsts.fRamp,
            1.f / ripConsts.fRamp,
            kRipBias,
            0); // Last one still unused.

        vShader->SetNumPipeConsts(4);
        vShader->SetPipeConst(0, plPipeConst::kWorldToNDC, plRipVS::kWorldToNDC);
        vShader->SetPipeConst(1, plPipeConst::kCamPosWorld, plRipVS::kCameraPos);
        vShader->SetPipeConst(2, plPipeConst::kLocalToWorld, plRipVS::kLocalToWorld);
        vShader->SetPipeConst(3, plPipeConst::kFogSet, plRipVS::kFogSet);

//      vShader->SetShaderFileName("sha/vs_WaveRip.inl");
//      vShader->SetDecl(plShaderTable::Decl(vs_WaveRip));

//      static const plShaderDecl decl("sha/vs_WaveRip7.inl");
//      vShader->SetDecl(&decl);
        vShader->SetDecl(plShaderTable::Decl(vs_WaveRip7));

        hsgResMgr::ResMgr()->SendRef(vShader->GetKey(), TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnRequest, 0, kRefRipVShader), plRefFlags::kActiveRef);

        hsAssert(vShader == fRipVShader, "Should have been set by SendRef");
    }

    IAddShaderToLayers(mat, 0, mat->GetNumLayers()-1, plLayRefMsg::kVertexShader, fRipVShader);

}

void plWaveSet7::IAddRipPixelShader(hsGMaterial* mat, const plRipVSConsts& ripConsts)
{
    if( !fRipPShader )
    {
        plShader* pShader = TRACKED_NEW plShader;
        char buff[256];
        sprintf(buff, "%s_RipPS", GetKey()->GetName());
        hsgResMgr::ResMgr()->NewKey(buff, pShader, GetKey()->GetUoid().GetLocation());
        pShader->SetIsPixelShader(true);
        
        pShader->SetNumConsts(0);

        pShader->SetInputFormat(0);
        pShader->SetOutputFormat(0);

//      pShader->SetShaderFileName("sha/ps_WaveRip.inl");
        pShader->SetDecl(plShaderTable::Decl(ps_WaveRip));

        hsgResMgr::ResMgr()->SendRef(pShader->GetKey(), TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnRequest, 0, kRefRipPShader), plRefFlags::kActiveRef);

        hsAssert(pShader == fRipPShader, "Should have been set by SendRef");
    }

    IAddShaderToLayers(mat, 0, mat->GetNumLayers()-1, plLayRefMsg::kPixelShader, fRipPShader);

}

plShader* plWaveSet7::ICreateDecalVShader(DecalVType t)
{
    if( !fDecalVShaders[t] )
    {
        static const char* fname[kNumDecalVShaders] = {
            "vs_WaveDec1Lay_7",
            "vs_WaveDec2Lay11_7",
            "vs_WaveDec2Lay12_7",
            "vs_WaveDecEnv_7"
        };
        static const plShaderDecl shaderDecls[kNumDecalVShaders] = {
            plShaderDecl("sha/vs_WaveDec1Lay_7.inl"),
            plShaderDecl("sha/vs_WaveDec2Lay11_7.inl"),
            plShaderDecl("sha/vs_WaveDec2Lay12_7.inl"),
            plShaderDecl("sha/vs_WaveDecEnv_7.inl")
        };

        static const plShaderID::ID shaderIDs[kNumDecalVShaders] = {
            vs_WaveDec1Lay_7,
            vs_WaveDec2Lay11_7,
            vs_WaveDec2Lay12_7,
            vs_WaveDecEnv_7
        };
        static const int numUVWs[kNumDecalVShaders] = {
            1,
            1,
            2,
            3
        };
        static const int numLayXfms[kNumDecalVShaders] = {
            1,
            2,
            2,
            1
        };


        plShader* vShader = TRACKED_NEW plShader;
        char buff[256];
        sprintf(buff, "%s_%s", GetKey()->GetName(), fname[t]);
        hsgResMgr::ResMgr()->NewKey(buff, vShader, GetKey()->GetUoid().GetLocation());
        vShader->SetIsPixelShader(false);
        
        vShader->SetInputFormat(numUVWs[t]); // This should really be one!!!
        vShader->SetOutputFormat(0);

        vShader->SetNumConsts(plWaveDecVS::kNumConsts);

        vShader->SetVector(plWaveDecVS::kSinConsts, 1.f, -1.f/6.f, 1.f/120.f, -1.f/5040.f);
        vShader->SetVector(plWaveDecVS::kCosConsts, 1.f, -1.f/2.f, 1.f/24.f, -1.f/720.f);
        vShader->SetVector(plWaveDecVS::kPiConsts, 1.f / (8.f*hsScalarPI*4.f*4.f), hsScalarPI/2.f, hsScalarPI, hsScalarPI*2.f);
        vShader->SetVector(plWaveDecVS::kNumericConsts, 0, 0.5f, 1.f, 2.f);

        hsVector3 waterOffset = State().fWaterOffset;
        vShader->SetVector(plWaveDecVS::kWaterLevel, 
            State().fWaterHeight + waterOffset.fX, 
            State().fWaterHeight + waterOffset.fY, 
            State().fWaterHeight + waterOffset.fZ, 
            State().fWaterHeight);

        hsVector3 maxAtten = State().fMaxAtten;
        hsVector3 minAtten = State().fMinAtten;
        hsVector3 depthFalloff = State().fDepthFalloff;
        vShader->SetVector(plWaveDecVS::kDepthFalloff,
            (maxAtten.fX - minAtten.fX) / depthFalloff.fX,
            (maxAtten.fY - minAtten.fY) / depthFalloff.fY,
            (maxAtten.fZ - minAtten.fZ) / depthFalloff.fZ,
            1.f
            );
        vShader->SetVector(plWaveDecVS::kMinAtten,
            minAtten.fX,
            minAtten.fY,
            minAtten.fZ,
            0.f
            );

        plConst(hsScalar) kBias(0.1);
        vShader->SetVector(plWaveDecVS::kBias,
            kBias,
            0,
            0,
            0); // Last one still unused.

        const int kNumPipe = 5;
        vShader->SetNumPipeConsts(kNumPipe + numLayXfms[t]);
        vShader->SetPipeConst(0, plPipeConst::kWorldToNDC, plWaveDecVS::kWorldToNDC);
        vShader->SetPipeConst(1, plPipeConst::kLocalToWorld, plWaveDecVS::kLocalToWorld);
        vShader->SetPipeConst(2, plPipeConst::kLayRuntime, plWaveDecVS::kMatColor);
        vShader->SetPipeConst(3, plPipeConst::kCamPosWorld, plWaveDecVS::kCameraPos);
        vShader->SetPipeConst(4, plPipeConst::kFogSet, plWaveDecVS::kFogSet);
        int i;
        for( i = 0; i < numLayXfms[t]; i++ )
        {
            vShader->SetPipeConst(kNumPipe + i, plPipeConst::Type(plPipeConst::kTex2x4_0+i), plWaveDecVS::kTex0Transform + i);
        }

//      vShader->SetDecl(&shaderDecls[t]);
        vShader->SetDecl(plShaderTable::Decl(shaderIDs[t]));

        hsgResMgr::ResMgr()->SendRef(vShader->GetKey(), TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnRequest, t, kRefDecVShader), plRefFlags::kActiveRef);

        hsAssert(vShader == fDecalVShaders[t], "Should have been set by SendRef");
    }
    return fDecalVShaders[t];
}

plShader* plWaveSet7::IGetDecalVShader(hsGMaterial* mat)
{
    if( mat->GetLayer(0)->GetShadeFlags() & hsGMatState::kShadeEnvironMap )
        return ICreateDecalVShader(kDecalVEnv);

    switch( mat->GetNumLayers() )
    {
    case 1:
        return ICreateDecalVShader(kDecalV1Lay);
    case 2:
        hsAssert(mat->GetLayer(0)->GetUVWSrc() == 0, "First layer must use uvw channel 1");
        hsAssert(mat->GetLayer(1)->GetUVWSrc() < 2, "Second layer must use uvw channel 1 or 2");
        switch( mat->GetLayer(1)->GetUVWSrc() )
        {
        case 0:
            return ICreateDecalVShader(kDecalV2Lay11);
        case 1:
            return ICreateDecalVShader(kDecalV2Lay12);
        default:
            return nil;
        }
        break;
    default:
        hsAssert(false, "Only 1 or 2 layers currently supported");
    }
    return nil;
}

plShader* plWaveSet7::ICreateDecalPShader(DecalPType t)
{
    if( !fDecalPShaders[t] )
    {
        static const char* fname[kNumDecalPShaders] = {
            "ps_CbaseAbase",
            "ps_CalphaAbase",
            "ps_CalphaAMult",
            "ps_CalphaAadd",
            "ps_CaddAbase",
            "ps_CaddAMult",
            "ps_CaddAAdd",
            "ps_CmultAbase",
            "ps_CmultAMult",
            "ps_CmultAAdd",
            "ps_WaveDecEnv"
        };
        static const plShaderID::ID shaderIDs[kNumDecalPShaders] = {
            ps_CbaseAbase,
            ps_CalphaAbase,
            ps_CalphaAMult,
            ps_CalphaAadd,
            ps_CaddAbase,
            ps_CaddAMult,
            ps_CaddAAdd,
            ps_CmultAbase,
            ps_CmultAMult,
            ps_CmultAAdd,
            ps_WaveDecEnv
        };

        plShader* pShader = TRACKED_NEW plShader;

        char buff[256];
        sprintf(buff, "%s_%s", GetKey()->GetName(), fname[t]);
        hsgResMgr::ResMgr()->NewKey(buff, pShader, GetKey()->GetUoid().GetLocation());
        pShader->SetIsPixelShader(true);

//      sprintf(buff, "sha/%s.inl", fname[t]);
//      pShader->SetShaderFileName(buff);
        pShader->SetDecl(plShaderTable::Decl(shaderIDs[t]));

        hsgResMgr::ResMgr()->SendRef(pShader->GetKey(), TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnRequest, t, kRefDecPShader), plRefFlags::kActiveRef);

        hsAssert(fDecalPShaders[t] == pShader, "Should have been set by SendRef");
    }
    return fDecalPShaders[t];

}

plShader* plWaveSet7::IGetDecalPShader(hsGMaterial* mat)
{
    if( mat->GetLayer(0)->GetShadeFlags() & hsGMatState::kShadeEnvironMap )
        return ICreateDecalPShader(kDecalPEnv);

    hsAssert(mat->GetNumLayers() < 3, "Only 2 layers supported on water decal");
    if( mat->GetNumLayers() >= 3 )
        return nil;

    if( mat->GetNumLayers() == 1 )
        return ICreateDecalPShader(kDecalPBB);

    DecalPType t = kNumDecalPShaders; // Initialize to illegal value.

    switch( mat->GetLayer(1)->GetBlendFlags() & (hsGMatState::kBlendMask | hsGMatState::kBlendAlphaMult | hsGMatState::kBlendAlphaAdd) )
    {
    case 0:
    case hsGMatState::kBlendTest:
    case hsGMatState::kBlendAlpha:
        t = kDecalPaB;
        break;
    case hsGMatState::kBlendAdd:
        t = kDecalPAB;
        break;
    case hsGMatState::kBlendMult:
        t = kDecalPMB;
        break;
    case hsGMatState::kBlendAlpha | hsGMatState::kBlendAlphaAdd:
        t = kDecalPaA;
        break;
    case hsGMatState::kBlendAdd | hsGMatState::kBlendAlphaAdd:
        t = kDecalPAA;
        break;
    case hsGMatState::kBlendMult | hsGMatState::kBlendAlphaAdd:
        t = kDecalPMA;
        break;
    case hsGMatState::kBlendAlpha | hsGMatState::kBlendAlphaMult:
        t = kDecalPaM;
        break;
    case hsGMatState::kBlendAdd | hsGMatState::kBlendAlphaMult:
        t = kDecalPAM;
        break;
    case hsGMatState::kBlendMult | hsGMatState::kBlendAlphaMult:
        t = kDecalPMM;
        break;

    default:
        hsAssert(false, "Unsupported layer blend mode");
        return nil;
    }

    return ICreateDecalPShader(t);
}

void plWaveSet7::IUpdateShaders(plPipeline* pipe, const hsMatrix44& l2w, const hsMatrix44& w2l)
{
    IUpdateBiasVShader();

    IUpdateBumpVShader(pipe, l2w, w2l);

    IUpdateBumpPShader(pipe, l2w, w2l);

    IUpdateRipPShader(pipe, l2w, w2l);

    IUpdateRipVShader(pipe, l2w, w2l);

    IUpdateShoreVShader(pipe, l2w, w2l);

    IUpdateFixedVShader(pipe, l2w, w2l);

    IUpdateFixedPShader(pipe, l2w, w2l);

    IUpdateDecVShaders(pipe, l2w, w2l);
}

void plWaveSet7::IUpdateBumpPShader(plPipeline* pipe, const hsMatrix44& l2w, const hsMatrix44& w2l)
{
    plCONST(Int32)      skip(0);
    int i;
    for( i = 0; i < kNumBumpShaders; i++ )
    {

        int j;
        for( j = 0; j < kBumpPerPass; j++ )
        {
            int iTex = i*kBumpPerPass + j;

            hsVector3 specVec = State().fSpecVec;
            hsScalar scale = 1.f / (hsScalar(kNumBumpShaders) + specVec[State().kNoise]);

            hsScalar maxLen = TexState().fMaxLength * kCompositeSize / State().fRippleScale;
            hsScalar rescale = fTexWaves[iTex].fLen / maxLen;

            hsScalar bias = 0.5f * scale;
            fBumpPShader[i]->SetVector(plBumpPS::kHalfOne, scale, scale, 1.f, 1.f);
            fBumpPShader[i]->SetVector(plBumpPS::kBias, bias, bias, 1.f, 1.f);

            hsScalar layScale = skip & (1 << iTex) ? 0.f : (1.f / hsScalar(kBumpPerPass));
            layScale *= fTexWaveFade[iTex];

            fBumpPShader[i]->SetVector(plBumpPS::kWave0 + j,
                -fTexWaves[iTex].fDirX * layScale,
                -fTexWaves[iTex].fDirY * layScale,
                1.f,
                1.f);
        }
    }

}

void plWaveSet7::IUpdateBumpVShader(plPipeline* pipe, const hsMatrix44& l2w, const hsMatrix44& w2l)
{
}

static inline hsScalar IRound(hsScalar f)
{
    return hsScalar(int(f + (f > 0 ? 0.5f : -0.5f)));
}

void plWaveSet7::IUpdateBiasVShader()
{

    if( fBiasVShader )
    {
        // Can't just use GetDelSysSeconds() or else we lose time if we skip a frame render because of high FPS.
        hsScalar dt = fLastTime > 0 ? hsScalar(fCurrTime - fLastTime) : hsTimer::GetDelSysSeconds();
        plConst(hsScalar) kRate(-0.1f);
        hsScalar tx = kRate * dt;
        hsScalar ty = kRate * dt;
        plConst(hsScalar) kScaleU(4.f);
        plConst(hsScalar) kScaleV(1.f);
        tx += fBiasVShader->GetFloat(plBiasVS::kTexU0, 3);
        tx -= hsScalar(int(tx));

        ty += fBiasVShader->GetFloat(plBiasVS::kTexV0, 3);
        ty -= hsScalar(int(ty));

        hsScalar scale = 1.f + (4.f - 1.f) * TexState().fAngleDev/hsScalarPI;

        hsScalar m00 = IRound(fWindDir.fY * scale);
        hsScalar m01 = IRound(fWindDir.fX * scale);
        hsScalar m10 = IRound(-fWindDir.fX * 4.f);
        hsScalar m11 = IRound(fWindDir.fY * 4.f);

        fBiasVShader->SetVector(plBiasVS::kTexU0,
            m00,
            m01,
            0,
            tx);
        fBiasVShader->SetVector(plBiasVS::kTexV0,
            m10,
            m11,
            0,
            ty);

        plConst(hsScalar) kUpperNoiseOffU(0.f);
        plConst(hsScalar) kUpperNoiseOffV(0.3f);
        fBiasVShader->SetVector(plBiasVS::kTexU1,
            m00,
            m01,
            0,
            -tx + kUpperNoiseOffU);
        fBiasVShader->SetVector(plBiasVS::kTexV1,
            m10,
            m11,
            0,
            ty + kUpperNoiseOffV);

        hsVector3 specVec = State().fSpecVec;
        hsScalar biasScale = 0.5f * specVec[State().kNoise] / (hsScalar(kNumBumpShaders) + specVec[State().kNoise]);
        fBiasVShader->SetVector(plBiasVS::kScaleBias,
            biasScale,
            biasScale,
            0.f,
            1.f);
    }
}

void plWaveSet7::IUpdateFixedPShader(plPipeline* pipe, const hsMatrix44& l2w, const hsMatrix44& w2l)
{
}

void plWaveSet7::IUpdateRipPShader(plPipeline* pipe, const hsMatrix44& l2w, const hsMatrix44& w2l)
{
    // Nothing to do
}

void plWaveSet7::IUpdateRipVShader(plPipeline* pipe, const hsMatrix44& l2w, const hsMatrix44& w2l)
{
    if( fRipVShader )
    {
        fRipVShader->SetVector(plRipVS::kFrequency,
            fWorldWaves[0].fFreq,
            fWorldWaves[1].fFreq,
            fWorldWaves[2].fFreq,
            fWorldWaves[3].fFreq);

        fRipVShader->SetVector(plRipVS::kPhase,
            fWorldWaves[0].fPhase,
            fWorldWaves[1].fPhase,
            fWorldWaves[2].fPhase,
            fWorldWaves[3].fPhase);

        fRipVShader->SetVector(plRipVS::kAmplitude,
            fWorldWaves[0].fAmplitude,
            fWorldWaves[1].fAmplitude,
            fWorldWaves[2].fAmplitude,
            fWorldWaves[3].fAmplitude);

        fRipVShader->SetVector(plRipVS::kDirectionX,
            fWorldWaves[0].fDir.fX,
            fWorldWaves[1].fDir.fX,
            fWorldWaves[2].fDir.fX,
            fWorldWaves[3].fDir.fX);

        fRipVShader->SetVector(plRipVS::kDirectionY,
            fWorldWaves[0].fDir.fY,
            fWorldWaves[1].fDir.fY,
            fWorldWaves[2].fDir.fY,
            fWorldWaves[3].fDir.fY);

        fRipVShader->SetVector(plRipVS::kWindRot,
            fWindDir.fY,
            -fWindDir.fX,
            fWindDir.fX,
            0);

        fRipVShader->SetVector(plRipVS::kLengths,
            fWorldWaves[0].fLength,
            fWorldWaves[1].fLength,
            fWorldWaves[2].fLength,
            fWorldWaves[3].fLength);

        fRipVShader->SetFloat(plRipVS::kLifeConsts, 1, float(hsTimer::GetSysSeconds()));
        float normQ[kNumWaves];
        int i; 
        for( i = 0; i < kNumWaves; i++ )
        {
            normQ[i] = GeoState().fChop / (2.f*hsScalarPI * GeoState().fAmpOverLen * kNumWaves);
        }

        fRipVShader->SetVector(plRipVS::kQADirX,
            fWorldWaves[0].fAmplitude * fWorldWaves[0].fDir.fX * normQ[0],
            fWorldWaves[1].fAmplitude * fWorldWaves[1].fDir.fX * normQ[1],
            fWorldWaves[2].fAmplitude * fWorldWaves[2].fDir.fX * normQ[2],
            fWorldWaves[3].fAmplitude * fWorldWaves[3].fDir.fX * normQ[3]);

        fRipVShader->SetVector(plRipVS::kQADirY,
            fWorldWaves[0].fAmplitude * fWorldWaves[0].fDir.fY * normQ[0],
            fWorldWaves[1].fAmplitude * fWorldWaves[1].fDir.fY * normQ[1],
            fWorldWaves[2].fAmplitude * fWorldWaves[2].fDir.fY * normQ[2],
            fWorldWaves[3].fAmplitude * fWorldWaves[3].fDir.fY * normQ[3]);

    }
}

void plWaveSet7::IUpdateDecVShaders(plPipeline* pipe, const hsMatrix44& l2w, const hsMatrix44& w2l)
{
    int i;
    for( i = 0; i < kNumDecalVShaders; i++ )
        IUpdateDecVShader(i, pipe);
}

void plWaveSet7::IUpdateDecVShader(int t, plPipeline* pipe)
{
    plShader* shader = fDecalVShaders[t];
    if( shader )
    {
        shader->SetVector(plWaveDecVS::kFrequency,
            fWorldWaves[0].fFreq,
            fWorldWaves[1].fFreq,
            fWorldWaves[2].fFreq,
            fWorldWaves[3].fFreq);

        shader->SetVector(plWaveDecVS::kPhase,
            fWorldWaves[0].fPhase,
            fWorldWaves[1].fPhase,
            fWorldWaves[2].fPhase,
            fWorldWaves[3].fPhase);

        shader->SetVector(plWaveDecVS::kAmplitude,
            fWorldWaves[0].fAmplitude,
            fWorldWaves[1].fAmplitude,
            fWorldWaves[2].fAmplitude,
            fWorldWaves[3].fAmplitude);

        shader->SetVector(plWaveDecVS::kDirectionX,
            fWorldWaves[0].fDir.fX,
            fWorldWaves[1].fDir.fX,
            fWorldWaves[2].fDir.fX,
            fWorldWaves[3].fDir.fX);

        shader->SetVector(plWaveDecVS::kDirectionY,
            fWorldWaves[0].fDir.fY,
            fWorldWaves[1].fDir.fY,
            fWorldWaves[2].fDir.fY,
            fWorldWaves[3].fDir.fY);

        shader->SetVector(plWaveDecVS::kLengths,
            fWorldWaves[0].fLength,
            fWorldWaves[1].fLength,
            fWorldWaves[2].fLength,
            fWorldWaves[3].fLength);

        if( t == kDecalVEnv )
        {
            hsPoint3 worldCam = pipe->GetViewTransform().GetCameraToWorld().GetTranslate();

            hsPoint3 envCenter(State().fEnvCenter);

            hsScalar envRadius = State().fEnvRadius;

            hsVector3 camToCen(&envCenter, &worldCam);
            hsScalar G = camToCen.MagnitudeSquared() - envRadius * envRadius;
            shader->SetVectorW(plWaveDecVS::kEnvAdjust, camToCen, G);

        }

        float normQ[kNumWaves];
        int i;
        for( i = 0; i < kNumWaves; i++ )
        {
            normQ[i] = GeoState().fChop / (2.f*hsScalarPI * GeoState().fAmpOverLen * kNumWaves);
        }

        shader->SetVector(plWaveDecVS::kQADirX,
            fWorldWaves[0].fAmplitude * fWorldWaves[0].fDir.fX * normQ[0],
            fWorldWaves[1].fAmplitude * fWorldWaves[1].fDir.fX * normQ[1],
            fWorldWaves[2].fAmplitude * fWorldWaves[2].fDir.fX * normQ[2],
            fWorldWaves[3].fAmplitude * fWorldWaves[3].fDir.fX * normQ[3]);

        shader->SetVector(plWaveDecVS::kQADirY,
            fWorldWaves[0].fAmplitude * fWorldWaves[0].fDir.fY * normQ[0],
            fWorldWaves[1].fAmplitude * fWorldWaves[1].fDir.fY * normQ[1],
            fWorldWaves[2].fAmplitude * fWorldWaves[2].fDir.fY * normQ[2],
            fWorldWaves[3].fAmplitude * fWorldWaves[3].fDir.fY * normQ[3]);

        shader->SetVector(plWaveDecVS::kDirXW,
            fWorldWaves[0].fDir.fX * fWorldWaves[0].fFreq,
            fWorldWaves[1].fDir.fX * fWorldWaves[1].fFreq,
            fWorldWaves[2].fDir.fX * fWorldWaves[2].fFreq,
            fWorldWaves[3].fDir.fX * fWorldWaves[3].fFreq);

        shader->SetVector(plWaveDecVS::kDirYW,
            fWorldWaves[0].fDir.fY * fWorldWaves[0].fFreq,
            fWorldWaves[1].fDir.fY * fWorldWaves[1].fFreq,
            fWorldWaves[2].fDir.fY * fWorldWaves[2].fFreq,
            fWorldWaves[3].fDir.fY * fWorldWaves[3].fFreq);

        shader->SetVector(plWaveDecVS::kWK,
            normQ[0] * fWorldWaves[0].fFreq,
            normQ[1] * fWorldWaves[1].fFreq,
            normQ[2] * fWorldWaves[2].fFreq,
            normQ[3] * fWorldWaves[3].fFreq);

        shader->SetVector(plWaveDecVS::kDirXSqKW,
            fWorldWaves[0].fDir.fX * fWorldWaves[0].fDir.fX * fWorldWaves[0].fFreq * normQ[0],
            fWorldWaves[1].fDir.fX * fWorldWaves[1].fDir.fX * fWorldWaves[1].fFreq * normQ[1],
            fWorldWaves[2].fDir.fX * fWorldWaves[2].fDir.fX * fWorldWaves[2].fFreq * normQ[2],
            fWorldWaves[3].fDir.fX * fWorldWaves[3].fDir.fX * fWorldWaves[3].fFreq * normQ[3]);

        shader->SetVector(plWaveDecVS::kDirXDirYKW,
            fWorldWaves[0].fDir.fX * fWorldWaves[0].fDir.fY * fWorldWaves[0].fFreq * normQ[0],
            fWorldWaves[1].fDir.fX * fWorldWaves[1].fDir.fY * fWorldWaves[1].fFreq * normQ[1],
            fWorldWaves[2].fDir.fX * fWorldWaves[2].fDir.fY * fWorldWaves[2].fFreq * normQ[2],
            fWorldWaves[3].fDir.fX * fWorldWaves[3].fDir.fY * fWorldWaves[3].fFreq * normQ[3]);

        shader->SetVector(plWaveDecVS::kDirYSqKW,
            fWorldWaves[0].fDir.fY * fWorldWaves[0].fDir.fY * fWorldWaves[0].fFreq * normQ[0],
            fWorldWaves[1].fDir.fY * fWorldWaves[1].fDir.fY * fWorldWaves[1].fFreq * normQ[1],
            fWorldWaves[2].fDir.fY * fWorldWaves[2].fDir.fY * fWorldWaves[2].fFreq * normQ[2],
            fWorldWaves[3].fDir.fY * fWorldWaves[3].fDir.fY * fWorldWaves[3].fFreq * normQ[3]);

    }
}

void plWaveSet7::IUpdateShoreVShader(plPipeline* pipe, const hsMatrix44& l2w, const hsMatrix44& w2l)
{
    if( fShoreVShader )
    {

        fShoreVShader->SetVector(plShoreVS::kFrequency,
            fWorldWaves[0].fFreq,
            fWorldWaves[1].fFreq,
            fWorldWaves[2].fFreq,
            fWorldWaves[3].fFreq);

        fShoreVShader->SetVector(plShoreVS::kPhase,
            fWorldWaves[0].fPhase,
            fWorldWaves[1].fPhase,
            fWorldWaves[2].fPhase,
            fWorldWaves[3].fPhase);

        fShoreVShader->SetVector(plShoreVS::kAmplitude,
            fWorldWaves[0].fAmplitude,
            fWorldWaves[1].fAmplitude,
            fWorldWaves[2].fAmplitude,
            fWorldWaves[3].fAmplitude);

        fShoreVShader->SetVector(plShoreVS::kDirectionX,
            fWorldWaves[0].fDir.fX,
            fWorldWaves[1].fDir.fX,
            fWorldWaves[2].fDir.fX,
            fWorldWaves[3].fDir.fX);

        fShoreVShader->SetVector(plShoreVS::kDirectionY,
            fWorldWaves[0].fDir.fY,
            fWorldWaves[1].fDir.fY,
            fWorldWaves[2].fDir.fY,
            fWorldWaves[3].fDir.fY);

        hsVector3 waterOffset = State().fWaterOffset;
        fShoreVShader->SetVector(plShoreVS::kWaterLevel, 
            State().fWaterHeight + waterOffset.fX, 
            State().fWaterHeight + waterOffset.fY, 
            State().fWaterHeight + waterOffset.fZ, 
            State().fWaterHeight);

        hsVector3 maxAtten = State().fMaxAtten;
        hsVector3 minAtten = State().fMinAtten;
        hsVector3 depthFalloff = State().fDepthFalloff;
        fShoreVShader->SetVector(plShoreVS::kDepthFalloff,
            (maxAtten.fX - minAtten.fX) / depthFalloff.fX,
            (maxAtten.fY - minAtten.fY) / depthFalloff.fY,
            (maxAtten.fZ - minAtten.fZ) / depthFalloff.fZ,
            1.f
            );
        fShoreVShader->SetVector(plShoreVS::kMinAtten,
            minAtten.fX,
            minAtten.fY,
            minAtten.fZ,
            0.f
            );

        fShoreVShader->SetVector(plShoreVS::kLengths,
            fWorldWaves[0].fLength,
            fWorldWaves[1].fLength,
            fWorldWaves[2].fLength,
            fWorldWaves[3].fLength);

        plConst(hsScalar) kK1(2.f);
        plConst(hsScalar) kK2(5.f);
        hsScalar negK1OverK2Sq = -kK1 / (kK2 * kK2);
        fShoreVShader->SetVector(plShoreVS::kIncline, negK1OverK2Sq, kK1, 0.f, 0.f);

        float normQ[kNumWaves];
        int i;
        for( i = 0; i < kNumWaves; i++ )
        {
            normQ[i] = GeoState().fChop / (2.f*hsScalarPI * GeoState().fAmpOverLen * kNumWaves);
        }

        fShoreVShader->SetVector(plShoreVS::kQADirX,
            fWorldWaves[0].fAmplitude * fWorldWaves[0].fDir.fX * normQ[0],
            fWorldWaves[1].fAmplitude * fWorldWaves[1].fDir.fX * normQ[1],
            fWorldWaves[2].fAmplitude * fWorldWaves[2].fDir.fX * normQ[2],
            fWorldWaves[3].fAmplitude * fWorldWaves[3].fDir.fX * normQ[3]);

        fShoreVShader->SetVector(plShoreVS::kQADirY,
            fWorldWaves[0].fAmplitude * fWorldWaves[0].fDir.fY * normQ[0],
            fWorldWaves[1].fAmplitude * fWorldWaves[1].fDir.fY * normQ[1],
            fWorldWaves[2].fAmplitude * fWorldWaves[2].fDir.fY * normQ[2],
            fWorldWaves[3].fAmplitude * fWorldWaves[3].fDir.fY * normQ[3]);


        if( fTrialUpdate & kRemakeBubble )
            IRefillBubbleShoreTex();
        if( fTrialUpdate & kRemakeEdge )
            IRefillEdgeShoreTex();
    }
}

void plWaveSet7::IUpdateFixedVShader(plPipeline* pipe, const hsMatrix44& l2w, const hsMatrix44& w2l)
{
    if( fFixedVShader )
    {
        fFixedVShader->SetVector(plFixedVS7::kFrequency,
            fWorldWaves[0].fFreq,
            fWorldWaves[1].fFreq,
            fWorldWaves[2].fFreq,
            fWorldWaves[3].fFreq);

        fFixedVShader->SetVector(plFixedVS7::kPhase,
            fWorldWaves[0].fPhase,
            fWorldWaves[1].fPhase,
            fWorldWaves[2].fPhase,
            fWorldWaves[3].fPhase);

        fFixedVShader->SetVector(plFixedVS7::kAmplitude,
            fWorldWaves[0].fAmplitude,
            fWorldWaves[1].fAmplitude,
            fWorldWaves[2].fAmplitude,
            fWorldWaves[3].fAmplitude);

        fFixedVShader->SetVector(plFixedVS7::kDirectionX,
            fWorldWaves[0].fDir.fX,
            fWorldWaves[1].fDir.fX,
            fWorldWaves[2].fDir.fX,
            fWorldWaves[3].fDir.fX);

        fFixedVShader->SetVector(plFixedVS7::kDirectionY,
            fWorldWaves[0].fDir.fY,
            fWorldWaves[1].fDir.fY,
            fWorldWaves[2].fDir.fY,
            fWorldWaves[3].fDir.fY);

        plCONST(hsScalar) kEnvRadius(500.f);
        hsScalar envRadius = State().fEnvRadius;

        hsPoint3 worldCam = pipe->GetViewTransform().GetCameraToWorld().GetTranslate();

        hsPoint3 envCenter(State().fEnvCenter);

        hsVector3 camToCen(&envCenter, &worldCam);
        hsScalar G = camToCen.MagnitudeSquared() - envRadius * envRadius;
        fFixedVShader->SetVectorW(plFixedVS7::kEnvAdjust, camToCen, G);

        hsScalar texScale = 1.f / State().fRippleScale;

        fFixedVShader->SetVector(plFixedVS7::kUVScale,
            texScale,
            0,
            0,
            0);

        hsScalar specAtten = State().fTexState.fAmpOverLen * hsScalarPI * 2.f;

        plCONST(hsScalar) kScaleHack(0.1f);
        hsScalar baseScale = kScaleHack;
//      baseScale *= hsScalar(kBumpPerPass) * (hsScalar(kNumBumpShaders) + State().fSpecVec[State().kNoise]);
        // Not sure what's right here. but we are currently scaling down by 1/(numBumpShaders + noise),
        // so I guess we want to scale up by that amount here. Not sure we shouldn't figuring in bumpperpass
        // on both, but at least now we're consistent.
        hsVector3 specVec = State().fSpecVec;
        baseScale *= (hsScalar(kNumBumpShaders) + specVec[State().kNoise]);
        baseScale *= (TexState().fChop + 1.f);


        hsScalar specStart = specVec[State().kSpecStart];
        hsScalar specEnd = specVec[State().kSpecEnd];
        if( specStart > specEnd )
            specEnd = specStart + 1.f;
        fFixedVShader->SetVector(plFixedVS7::kSpecAtten,
            -specEnd,
            1.f / (specStart - specEnd),
            baseScale * specAtten,
            0.f);

        hsColorRGBA envTint = State().fSpecularTint;
        if( fFixedLayers[0] && (fFixedLayers[0]->GetShadeFlags() & hsGMatState::kShadeSpecular) )
        {
            envTint *= fFixedLayers[0]->GetSpecularColor();
        }
        fFixedVShader->SetColor(plFixedVS::kEnvTint, envTint);

        fFixedVShader->SetVector(plFixedVS7::kWindRot,
            fWindDir.fY,
            -fWindDir.fX,
            fWindDir.fX,
            0);

        fFixedVShader->SetVector(plFixedVS7::kLengths,
            fWorldWaves[0].fLength,
            fWorldWaves[1].fLength,
            fWorldWaves[2].fLength,
            fWorldWaves[3].fLength);

        // These don't change often, but they can change. We're going
        // to be uploading them to the card anyway, might as well refresh
        // the sysmem shader to be safe.
        hsVector3 waterOffset = State().fWaterOffset;
        fFixedVShader->SetVector(plFixedVS7::kWaterLevel, 
            State().fWaterHeight + waterOffset.fX, 
            State().fWaterHeight + waterOffset.fY, 
            State().fWaterHeight + waterOffset.fZ, 
            State().fWaterHeight);

        hsVector3 maxAtten = State().fMaxAtten;
        hsVector3 minAtten = State().fMinAtten;
        hsVector3 depthFalloff = State().fDepthFalloff;
        fFixedVShader->SetVector(plFixedVS7::kDepthFalloff,
            (maxAtten.fX - minAtten.fX) / depthFalloff.fX,
            (maxAtten.fY - minAtten.fY) / depthFalloff.fY,
            (maxAtten.fZ - minAtten.fZ) / depthFalloff.fZ,
            1.f
            );
        fFixedVShader->SetVector(plFixedVS7::kMinAtten,
            minAtten.fX,
            minAtten.fY,
            minAtten.fZ,
            0.f
            );

        float normQ[kNumWaves];
        int i;
        for( i = 0; i < kNumWaves; i++ )
        {
            normQ[i] = GeoState().fChop / (2.f*hsScalarPI * GeoState().fAmpOverLen * kNumWaves);
        }

        fFixedVShader->SetVector(plFixedVS7::kDirXK,
            fWorldWaves[0].fDir.fX * normQ[0],
            fWorldWaves[1].fDir.fX * normQ[1],
            fWorldWaves[2].fDir.fX * normQ[2],
            fWorldWaves[3].fDir.fX * normQ[3]);

        fFixedVShader->SetVector(plFixedVS7::kDirYK,
            fWorldWaves[0].fDir.fY * normQ[0],
            fWorldWaves[1].fDir.fY * normQ[1],
            fWorldWaves[2].fDir.fY * normQ[2],
            fWorldWaves[3].fDir.fY * normQ[3]);

        fFixedVShader->SetVector(plFixedVS7::kDirXW,
            fWorldWaves[0].fDir.fX * fWorldWaves[0].fFreq,
            fWorldWaves[1].fDir.fX * fWorldWaves[1].fFreq,
            fWorldWaves[2].fDir.fX * fWorldWaves[2].fFreq,
            fWorldWaves[3].fDir.fX * fWorldWaves[3].fFreq);

        fFixedVShader->SetVector(plFixedVS7::kDirYW,
            fWorldWaves[0].fDir.fY * fWorldWaves[0].fFreq,
            fWorldWaves[1].fDir.fY * fWorldWaves[1].fFreq,
            fWorldWaves[2].fDir.fY * fWorldWaves[2].fFreq,
            fWorldWaves[3].fDir.fY * fWorldWaves[3].fFreq);

        fFixedVShader->SetVector(plFixedVS7::kWK,
            normQ[0] * fWorldWaves[0].fFreq,
            normQ[1] * fWorldWaves[1].fFreq,
            normQ[2] * fWorldWaves[2].fFreq,
            normQ[3] * fWorldWaves[3].fFreq);

        fFixedVShader->SetVector(plFixedVS7::kDirXSqKW,
            fWorldWaves[0].fDir.fX * fWorldWaves[0].fDir.fX * fWorldWaves[0].fFreq * normQ[0],
            fWorldWaves[1].fDir.fX * fWorldWaves[1].fDir.fX * fWorldWaves[1].fFreq * normQ[1],
            fWorldWaves[2].fDir.fX * fWorldWaves[2].fDir.fX * fWorldWaves[2].fFreq * normQ[2],
            fWorldWaves[3].fDir.fX * fWorldWaves[3].fDir.fX * fWorldWaves[3].fFreq * normQ[3]);

        fFixedVShader->SetVector(plFixedVS7::kDirXDirYKW,
            fWorldWaves[0].fDir.fX * fWorldWaves[0].fDir.fY * fWorldWaves[0].fFreq * normQ[0],
            fWorldWaves[1].fDir.fX * fWorldWaves[1].fDir.fY * fWorldWaves[1].fFreq * normQ[1],
            fWorldWaves[2].fDir.fX * fWorldWaves[2].fDir.fY * fWorldWaves[2].fFreq * normQ[2],
            fWorldWaves[3].fDir.fX * fWorldWaves[3].fDir.fY * fWorldWaves[3].fFreq * normQ[3]);

        fFixedVShader->SetVector(plFixedVS7::kDirYSqKW,
            fWorldWaves[0].fDir.fY * fWorldWaves[0].fDir.fY * fWorldWaves[0].fFreq * normQ[0],
            fWorldWaves[1].fDir.fY * fWorldWaves[1].fDir.fY * fWorldWaves[1].fFreq * normQ[1],
            fWorldWaves[2].fDir.fY * fWorldWaves[2].fDir.fY * fWorldWaves[2].fFreq * normQ[2],
            fWorldWaves[3].fDir.fY * fWorldWaves[3].fDir.fY * fWorldWaves[3].fFreq * normQ[3]);


    }
}


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

void plWaveSet7::ICheckShoreMaterials()
{
    int i;
    for( i = 0; i < fShores.GetCount(); i++ )
        ICheckShoreMaterial(fShores[i]);
}

void plWaveSet7::ICheckShoreMaterial(plSceneObject* so)
{
    if( !so )
        return;

    const plDrawInterface* di = so->GetDrawInterface();
    if( !di )
        return;

    hsTArray<plAccessSpan> src;
    plAccessGeometry::Instance()->OpenRO(di, src);

    if( !src.GetCount() )
        return;

    int i;
    for( i = 0; i < src.GetCount(); i++ )
    {
        ISetupGraphShore(src[i].GetMaterial());
    }

    plAccessGeometry::Instance()->Close(src);

}

void plWaveSet7::ICheckDecalMaterials()
{
    int i;
    for( i = 0; i < fDecals.GetCount(); i++ )
        ICheckDecalMaterial(fDecals[i]);
}

void plWaveSet7::ICheckDecalMaterial(plSceneObject* so)
{
    if( !so )
        return;

    const plDrawInterface* di = so->GetDrawInterface();
    if( !di )
        return;

    hsTArray<plAccessSpan> src;
    plAccessGeometry::Instance()->OpenRO(di, src);

    if( !src.GetCount() )
        return;

    int i;
    for( i = 0; i < src.GetCount(); i++ )
    {
        ISetupDecal(src[i].GetMaterial());
    }

    plAccessGeometry::Instance()->Close(src);

}

void plWaveSet7::ICheckDecalEnvLayers(hsGMaterial* mat)
{
    // Mat needs to be set up as:
    //      Lay0 = bumpmap - uv is lookup into bumpmap
    //      Lay1 = bumpmap - uv is col0 of tangent2local
    //      Lay2 = bumpmap - uv is col1 of tangent2local
    //      Lay3 = fEnvMap - uv is col2 of tangent2local, except we'll ignore it and use the normal instead
    // Note we'll have to transpose that as part of creating tangent2world
    // Easiest way to check whether we've done this one already is checking whether
    // Lay3 == fEnvMap.

    // If we haven't done this already
    if( !fDecalVShaders[kDecalVEnv] || (mat->GetLayer(0)->GetVertexShader() != fDecalVShaders[kDecalVEnv]) )
    {
        plLayer* lay3 = nil;

        plMatRefMsg* refMsg;

        const int numLayers = mat->GetNumLayers();
        int i;
        for( i = numLayers-1; i >= 0; i-- )
        {
            plLayer* lay0 = plLayer::ConvertNoRef(mat->GetLayer(i)->BottomOfStack());
            lay0->SetBlendFlags(hsGMatState::kBlendAddColorTimesAlpha);
//          lay0->SetBlendFlags(hsGMatState::kBlendAlpha);
            lay0->SetMiscFlags(lay0->GetMiscFlags() | hsGMatState::kMiscRestartPassHere);

            // Repeat lay3 as i+1, i+2 and i+3
            // set base blend to timealphaadd

            // If we are just creating lay3, then by creating it we've added (appended) it to mat.
            // Otherwise, we need to add it as i+1.
            if( !lay3 )
            {
                lay3 = ICreateTotalEnvLayer(fEnvMap, mat, 3, "Dec");
                lay3->SetBlendFlags(hsGMatState::kBlendAlpha);
            }
            else
            {
                refMsg = TRACKED_NEW plMatRefMsg(mat->GetKey(), plRefMsg::kOnRequest, i+1, plMatRefMsg::kLayer | plMatRefMsg::kInsert);
                hsgResMgr::ResMgr()->SendRef(lay3->GetKey(), refMsg, plRefFlags::kActiveRef);
            }

            refMsg = TRACKED_NEW plMatRefMsg(mat->GetKey(), plRefMsg::kOnRequest, i+2, plMatRefMsg::kLayer | plMatRefMsg::kInsert);
            hsgResMgr::ResMgr()->SendRef(lay3->GetKey(), refMsg, plRefFlags::kActiveRef);

            refMsg = TRACKED_NEW plMatRefMsg(mat->GetKey(), plRefMsg::kOnRequest, i+3, plMatRefMsg::kLayer | plMatRefMsg::kInsert);
            hsgResMgr::ResMgr()->SendRef(lay3->GetKey(), refMsg, plRefFlags::kActiveRef);
        }
    }
}

void plWaveSet7::ISetupDecal(hsGMaterial* mat)
{
    if( mat->GetLayer(0)->GetShadeFlags() & hsGMatState::kShadeEnvironMap )
    {
        ICheckDecalEnvLayers(mat);
    }

    plShader* vShader = IGetDecalVShader(mat);
    if( mat->GetLayer(0)->GetVertexShader() != vShader )
        IAddShaderToLayers(mat, 0, -1, plLayRefMsg::kVertexShader, vShader);

    int i;
    for( i = 0; i < mat->GetNumLayers(); i++ )
    {
        plLayer* lay = plLayer::ConvertNoRef(mat->GetLayer(i)->BottomOfStack());
        if( lay )
            lay->SetZFlags(lay->GetZFlags() | hsGMatState::kZNoZWrite | hsGMatState::kZIncLayer);
    }

    // The FFP is actually quite adequate for the required blends,
    // but may not stay that way (if we allow fancier blends and what not).
    // So we can either use the Pixel Shaders or FFP, depending on this define.
// #define MF_USE_FFP
#ifndef MF_USE_FFP
    plShader* pShader = IGetDecalPShader(mat);
    if( mat->GetLayer(0)->GetPixelShader() != pShader )
        IAddShaderToLayers(mat, 0, -1, plLayRefMsg::kPixelShader, pShader);
#endif // MF_USE_FFP
}

void plWaveSet7::AddShoreTest(plKey& key)
{
    hsgResMgr::ResMgr()->SendRef(key, TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnCreate, -1, kRefShore), plRefFlags::kPassiveRef);

    plSceneObject* so = plSceneObject::ConvertNoRef(key->ObjectIsLoaded());
    ICheckShoreMaterial(so);
}

//////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////
hsBool plWaveSet7::SetupRippleMat(hsGMaterial* mat, const plRipVSConsts& ripConsts)
{
    // We'll assume that if we set the vertexshader, we set the pixelshader too.
    if( fRipVShader && (mat->GetLayer(0)->GetVertexShader() == fRipVShader) )
        return true;

    int i;
    for( i = 0; i < mat->GetNumLayers(); i++ )
    {
        plLayer* lay = plLayer::ConvertNoRef(mat->GetLayer(i)->BottomOfStack());
        if( lay )
            lay->SetZFlags(lay->GetZFlags() | hsGMatState::kZNoZWrite | hsGMatState::kZIncLayer);
    }

    IAddRipVertexShader(mat, ripConsts);
    IAddRipPixelShader(mat, ripConsts);

    return true;
}

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

plDrawableSpans* plWaveSet7::ICreateGraphDrawable(plDrawableSpans* drawable, hsGMaterial* mat, int nWid)
{
//  drawable->SetNativeProperty(plDrawable::kPropVolatile, true);

    const int nVerts = nWid * 2;

    hsTArray<hsPoint3> pos;
    hsTArray<hsVector3> norm;

    pos.SetCount(nVerts);
    norm.SetCount(nVerts);

#ifdef TEST_UVWS
    hsTArray<hsPoint3> uvw; // Are we actually ever going to use these uvws?
    uvw.SetCount(nVerts);
#endif // TEST_UVWS

    int i;
    for( i = 0; i < nWid; i++ )
    {
        int iDn = i << 1;
        int iUp = iDn + 1;

        hsScalar delX = hsScalar(i) / hsScalar(nWid-1);

        pos[iDn].fX = delX * 2.f - 1.f;
        pos[iDn].fY = -1.f;
        pos[iDn].fZ = 0.5f;

        norm[iDn].Set(0.f, 0.f, 1.f);

#ifdef TEST_UVWS
        uvw[iDn].fX = delX;
        uvw[iDn].fY = 0.f;
        uvw[iDn].fZ = 0.f;
#endif // TEST_UVWS

        pos[iUp].fX = delX * 2.f - 1.f;
        pos[iUp].fY = 1.f;
        pos[iUp].fZ = 0.5f;

        norm[iUp].Set(1.f, 0.f, 1.f);

#ifdef TEST_UVWS
        uvw[iUp].fX = delX;
        uvw[iUp].fY = 1.f;
        uvw[iUp].fZ = 0.f;
#endif // TEST_UVWS
    }

    const int nTris = (nWid-1) * 2;

    hsTArray<UInt16> idxArr;
    idxArr.SetCount(nTris * 3);

    UInt16* idx = idxArr.AcquireArray();

    int iBase = 0;
    for( i = 0; i < nTris; i += 2 )
    {
        *idx++ = i;
        *idx++ = i + 2;
        *idx++ = i + 1;

        *idx++ = i + 1;
        *idx++ = i + 2;
        *idx++ = i + 3;
    }


    plDrawableGenerator::GenerateDrawable( nVerts, pos.AcquireArray(), norm.AcquireArray(), 
#ifndef TEST_UVWS
                                                        nil, 0, 
#else // TEST_UVWS
                                                        uvw.AcquireArray(), 1,
#endif // TEST_UVWS
                                                        nil, false, nil,
                                                        nTris * 3, idxArr.AcquireArray(), 
                                                        mat, 
                                                        hsMatrix44::IdentityMatrix(), 
                                                        false,
                                                        nil,
                                                        drawable);

    return drawable;
}

plDrawableSpans* plWaveSet7::ICreateEmptyGraphDrawable(const char* name, UInt32 ref, int which)
{
    plDrawableSpans* drawable = TRACKED_NEW plDrawableSpans;
    char buff[256];
    sprintf(buff, "%s_%s_%d", GetKey()->GetName(), name, which);
    hsgResMgr::ResMgr()->NewKey(buff, drawable, GetKey()->GetUoid().GetLocation());

    hsgResMgr::ResMgr()->SendRef(drawable->GetKey(), TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnRequest, which, (Int8)ref), plRefFlags::kActiveRef);

    return drawable;
}

hsGMaterial* plWaveSet7::ICreateEmptyMaterial(const char* name, UInt32 ref, int which)
{
    hsGMaterial* mat = TRACKED_NEW hsGMaterial;

    char buff[256];
    sprintf(buff, "%s_%s_%d", GetKey()->GetName(), name, which);
    hsgResMgr::ResMgr()->NewKey(buff, mat, GetKey()->GetUoid().GetLocation());

    hsgResMgr::ResMgr()->SendRef(mat->GetKey(), TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnRequest, which, (Int8)ref), plRefFlags::kActiveRef);

    return mat;
}

plLayer* plWaveSet7::ICreateBlankLayer(const char* name, int suff)
{
    plLayer* lay = TRACKED_NEW plLayer;
    char buff[256];
    sprintf(buff, "%s_%s_%d", GetKey()->GetName(), name, suff);
    hsgResMgr::ResMgr()->NewKey(buff, lay, GetKey()->GetUoid().GetLocation());

    return lay;
}

plMipmap* plWaveSet7::ICreateBlankTex(const char* name, int width, int height, UInt32 ref)
{
    plMipmap* mipMap = TRACKED_NEW plMipmap(
        width, height,
        plMipmap::kARGB32Config,
        1, 
        plMipmap::kUncompressed,
        plMipmap::UncompressedInfo::kRGB8888);

    char buff[256];
    sprintf(buff, "%s_%s", GetKey()->GetName(), name);
    hsgResMgr::ResMgr()->NewKey(buff, mipMap, GetKey()->GetUoid().GetLocation());

    hsgResMgr::ResMgr()->SendRef(mipMap->GetKey(), TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnRequest, 0, (Int8)ref), plRefFlags::kActiveRef);

    return mipMap;
}

plMipmap* plWaveSet7::ICreateGraphShoreTex(int width, int height)
{
    // GraphShoreLayer has a texture with white color (possibly noised),
    // and a solid alpha.
    // Alpha is constant along U (width).
    // Alpha is 0 at base,
    //      ramps up to opaque
    //      then drops off sharply to transparent just before the top.

    // If we haven't already made one...
    if( !fGraphShoreTex )
    {
        plMipmap* mipMap = ICreateBlankTex("Graph", width, height, kRefGraphShoreTex);

        plConst(hsScalar) kRampFrac(0.4f);
        plConst(hsScalar) kTruncFrac(0.8f);
        const int rampEnd = int(kRampFrac * height + 0.5f);
        int truncEnd = int(kTruncFrac * height);
        if( truncEnd >= (height-1) )
            truncEnd = height-2;
        int j;
        for( j = 0; j < height; j++ )
        {
            UInt32 alpha = 255;
            plConst(int) kRampStart(4);
            if( j <= kRampStart )
            {
                alpha = 0;
            }
            else
            if( j - kRampStart < rampEnd )
            {
                alpha = ((j-kRampStart) * 255) / rampEnd;
            }
            else if( j > truncEnd )
            {
                alpha = 0;
            }
            UInt32 color = (alpha << 24)
                    | (0xff << 16)
                    | (0xff << 8)
                    | 0xff;

            int i;
            for( i = 0; i < width; i++ )
            {
                UInt32* val = mipMap->GetAddr32(i, j);
                *val = color;
            }
        }
    }

    return fGraphShoreTex;
}

void plWaveSet7::IRefillBubbleShoreTex()
{
    plMipmap* mipMap = fBubbleShoreTex;
    hsAssert(mipMap, "Refilling a non-existent bubble texture");

    const int width = mipMap->GetWidth();
    const int height = mipMap->GetHeight();

    // Initialize to white opaque. 
    memset(mipMap->GetAddr32(0,0), 0xff, width*height*sizeof(UInt32));

    plConst(int) kMinNumBub(1024);
    plConst(int) kMaxNumBub(6000);
    const int kNumBub = (int)(kMinNumBub + State().fWispiness * (kMaxNumBub - kMinNumBub));
    int k;
    for( k = 0; k < kNumBub; k++ )
    {
        // Select a random location.
        int iLoc = (int)(fRand.RandZeroToOne() * width);
        int jLoc = (int)(fRand.RandZeroToOne() * height);

        // Select a random radius
        plConst(hsScalar) kMinRad(2.f);
        plConst(hsScalar) kMaxRad(5.0f);
        int radius = int(kMinRad + fRand.RandZeroToOne() * (kMaxRad - kMinRad));
        hsScalar invRadiusSq = 1.f / hsScalar(radius*radius);

        // Carve out a hole.
        int j;
        for( j = -radius; j < radius; j++ )
        {
            int jj = jLoc + j;
            if( jj < 0 )
                jj += height;
            else if( jj >= height )
                jj -= height;

            int i;
            for( i = -radius; i < radius; i++ )
            {
                int ii = iLoc + i;
                if( ii < 0 )
                    ii += width;
                else if( ii >= width )
                    ii -= width;

                hsScalar f = hsScalar(i*i + j*j) * invRadiusSq;
                if( f > 1.f )
                    f = 1.f;
                plConst(hsScalar) kMinAlpha(0.8f);
                plConst(hsScalar) kMaxAlpha(1.f);
                f *= (kMaxAlpha - kMinAlpha);
                f += kMinAlpha;

                UInt32* val = mipMap->GetAddr32(ii, jj);
                UInt32 alpha = (*val) >> 24;
                alpha = UInt32(hsScalar(alpha) * f);
                *val &= 0x00ffffff;
                *val |= (alpha << 24);
            }
        }
    }

    const hsColorRGBA maxColor = State().fMaxColor;
    const hsColorRGBA minColor = State().fMinColor;
    int j;
    for( j = 0; j < height; j++ )
    {
        int i;
        for( i = 0; i < width; i++ )
        {
            UInt32* val = mipMap->GetAddr32(i, j);
            hsColorRGBA col;
            col.FromARGB32(*val);
            hsScalar alpha = col.a;
            col = maxColor - minColor;
            col *= alpha;
            col += minColor;

            *val = col.ToARGB32();

        }
    }
    mipMap->MakeDirty();

    fTrialUpdate &= ~kRemakeBubble;
}

plMipmap* plWaveSet7::ICreateBubbleShoreTex(int width, int height)
{
    // Bubble layer is white in color (or noised).
    // Alpha is just, well, random bubbles.
    // Tile in U and V

    // If we haven't already made one...
    if( !fBubbleShoreTex )
    {
        plMipmap* mipMap = ICreateBlankTex("Bubble", width, height, kRefBubbleShoreTex);

        IRefillBubbleShoreTex();
    }

    return fBubbleShoreTex;
}

void plWaveSet7::IRefillEdgeShoreTex()
{
    plMipmap* mipMap = fEdgeShoreTex;

    const int width = mipMap->GetWidth();
    const int height = mipMap->GetHeight();

    plConst(hsScalar) kCenter(0.8f);
    plConst(hsScalar) kRadius(0.025f);

    const int center = int(kCenter * height);

    const int radius = int(kRadius * height * State().fEdgeRadius);

    const int top = center + radius;
    const int bot = center - radius;
    const hsScalar invRadiusSq = 1.f / hsScalar(radius*radius);

    hsAssert(top < height-1, "Center too high or radius too big");

    const hsScalar maxAlpha = State().fEdgeOpac * 255.9f;
    int j;
    for( j = 0; j < height; j++ )
    {
        UInt32 alpha = 0;
        if( (j > bot)&&(j < top) )
        {
#if 0 // like x^2
            hsScalar a = hsScalar(j-center);
            a *= a;
            a *= invRadiusSq;
            a = 1.f - a;
#elif 1 // like 1/x^2
            hsScalar a = hsScalar(j-center);
            if( a < 0 )
                a = -a;
            a /= hsScalar(radius);
            a = 1.f - a;
            a *= a;
#else // like cos
            hsScalar a = hsScalar(j - center);
            a /= hsScalar(radius);
            a *= hsScalarPI;
            a = hsFastMath::CosInRange(a);
            a += 1.f;
            a *= 0.5f;
#endif

            alpha = UInt32(a * maxAlpha);
        }

        int i;
        for( i = 0; i < width; i++ )
        {
            UInt32* val = mipMap->GetAddr32(i, j);
            *val = (alpha << 24)
                | (alpha << 16)
                | (alpha << 8)
                | (alpha << 0);
        }
    }
    mipMap->MakeDirty();

    fTrialUpdate &= ~kRemakeEdge;
}

plMipmap* plWaveSet7::ICreateEdgeShoreTex(int width, int height)
{
    // Edge layer is solid white color.
    // Alpha is 0 from base almost up to where graph shore texture drops off
    // sharply. There, alpha ramps up and back down. Probably get a better look
    // from sqrt(k - d^2) (elliptical) than a linear ramp up and down.
    // Tile in U, clamp in V.

    // If we haven't already made one...
    if( !fEdgeShoreTex )
    {
        plMipmap* mipMap = ICreateBlankTex("Edge", width, height, kRefEdgeShoreTex);

        IRefillEdgeShoreTex();
    }

    return fEdgeShoreTex;
}

void plWaveSet7::ISetAsTexture(plLayer* lay, plBitmap* tex)
{
    hsAssert(lay && tex, "Trying to set nil texture or nil layer");
    plLayRefMsg* refMsg = TRACKED_NEW plLayRefMsg(lay->GetKey(), plRefMsg::kOnRequest, 0, plLayRefMsg::kTexture);   
    hsgResMgr::ResMgr()->SendRef(tex->GetKey(), refMsg, plRefFlags::kActiveRef);
}

void plWaveSet7::ICreateGraphShoreLayer(hsGMaterial* mat, int iPass)
{
    // GraphShoreLayer has a texture with white color (possibly noised),
    // and a solid alpha.
    // Alpha is constant along U (width).
    // Alpha is 0 at base,
    //      ramps up to opaque
    //      then drops off sharply to transparent just before the top.
    // Might as well make it pretty much 1 dimensional.
    //
    // Tile in U, clamp in V

    plLayer* lay = ICreateBlankLayer("Graph", iPass);

    // Set up it's state.
    // First pass just overwrites, from then on alpha blend onto.
    lay->SetBlendFlags(0);
    lay->SetZFlags(hsGMatState::kZNoZRead | hsGMatState::kZNoZWrite);
    lay->SetShadeFlags(hsGMatState::kShadeReallyNoFog
        | hsGMatState::kShadeNoProjectors
        | hsGMatState::kShadeNoShade
        | hsGMatState::kShadeWhite);
    lay->SetClampFlags(hsGMatState::kClampTextureV);
    lay->SetMiscFlags(hsGMatState::kMiscRestartPassHere);

    lay->SetAmbientColor(hsColorRGBA().Set(0.f, 0.f, 0.f, 1.f));
    lay->SetRuntimeColor(hsColorRGBA().Set(1.f, 1.f, 1.f, 1.f));
    lay->SetOpacity(1.f);

    lay->SetUVWSrc(0);

    // Now set the texture.
    plConst(int) kGraphWidth(1);
    plConst(int) kGraphHeight(512);
    plMipmap* tex = ICreateGraphShoreTex(kGraphWidth, kGraphHeight);
    ISetAsTexture(lay, tex);

    mat->AddLayerViaNotify(lay);

}

// Second layer is the bubbles
void plWaveSet7::ICreateGraphBubbleLayer(hsGMaterial* mat, int iPass)
{
    // Bubble layer is white in color (or noised).
    // Alpha is just, well, random bubbles.
    // Tile in U and V
    plLayer* lay = ICreateBlankLayer("Bubble", iPass);

    // Set up it's state.
    lay->SetBlendFlags(hsGMatState::kBlendAlpha);
    lay->SetZFlags(hsGMatState::kZNoZRead | hsGMatState::kZNoZWrite);
    lay->SetShadeFlags(hsGMatState::kShadeReallyNoFog
        | hsGMatState::kShadeNoProjectors
        | hsGMatState::kShadeNoShade
        | hsGMatState::kShadeWhite);
    lay->SetClampFlags(0);
    lay->SetMiscFlags(0);

    lay->SetAmbientColor(hsColorRGBA().Set(0.f, 0.f, 0.f, 1.f));
    lay->SetRuntimeColor(hsColorRGBA().Set(1.f, 1.f, 1.f, 1.f));
    lay->SetOpacity(1.f);

    lay->SetUVWSrc(0);

    // Now set the texture
    plConst(int) kBubWidth(128);
    plConst(int) kBubHeight(128);
    plMipmap* tex = ICreateBubbleShoreTex(kBubWidth, kBubHeight);
    ISetAsTexture(lay, tex);

    mat->AddLayerViaNotify(lay);
}

// Third layer is the alpha leading edge
void plWaveSet7::ICreateGraphEdgeLayer(hsGMaterial* mat, int iPass)
{
    // Edge layer is solid white color.
    // Alpha is 0 from base almost up to where graph shore texture drops off
    // sharply. There, alpha ramps up and back down. Probably get a better look
    // from sqrt(k - d^2) (elliptical) than a linear ramp up and down.
    // Tile in U, clamp in V.
    plLayer* lay = ICreateBlankLayer("Edge", iPass);

    // Set up it's state.
    lay->SetBlendFlags(hsGMatState::kBlendAlpha);
    lay->SetZFlags(hsGMatState::kZNoZRead | hsGMatState::kZNoZWrite);
    lay->SetShadeFlags(hsGMatState::kShadeReallyNoFog
        | hsGMatState::kShadeNoProjectors
        | hsGMatState::kShadeNoShade
        | hsGMatState::kShadeWhite);
    lay->SetClampFlags(hsGMatState::kClampTextureV);
    lay->SetMiscFlags(0);

    lay->SetAmbientColor(hsColorRGBA().Set(0.f, 0.f, 0.f, 1.f));
    lay->SetRuntimeColor(hsColorRGBA().Set(1.f, 1.f, 1.f, 1.f));
    lay->SetOpacity(1.f);

    lay->SetUVWSrc(0);

    // Now set the texture.
    plConst(int) kEdgeWidth(1);
    plConst(int) kEdgeHeight(512);
    plMipmap* tex = ICreateEdgeShoreTex(kEdgeWidth, kEdgeHeight);
    ISetAsTexture(lay, tex);

    mat->AddLayerViaNotify(lay);
}

void plWaveSet7::ICreateGraphShoreMaterials()
{
    int i;
    for( i = 0; i < kGraphShorePasses; i++ )
    {
        // Create our material
        // and send ourselves a ref.
        hsGMaterial* mat = ICreateEmptyMaterial("GraphShoreMat", kRefGraphShoreMat, i);
        
        // GraphShoreMat's are the materials used to generate the shore texture layers
        // which are then used on rendering the shore to the screen.

        // Create 3 layers
        // First layer is the graph shore
        ICreateGraphShoreLayer(mat, i);

        // Second layer is the bubbles
        ICreateGraphBubbleLayer(mat, i);

        // Third layer is the alpha leading edge
        ICreateGraphEdgeLayer(mat, i);

        IAddGraphVShader(mat, i);
        IAddGraphPShader(mat, i);

        IInitGraph(i);

        hsAssert(fGraphShoreMat[i] == mat, "Should have been processed in ICreateEmptyMaterial()");
    }

}

void plWaveSet7::IAddGraphVShader(hsGMaterial* mat, int iPass)
{
    if( !fGraphVShader[iPass] )
    {
        plShader* vShader = TRACKED_NEW plShader;
        char buff[256];
        sprintf(buff, "%s_GraphVS_%d", GetKey()->GetName(), iPass);
        hsgResMgr::ResMgr()->NewKey(buff, vShader, GetKey()->GetUoid().GetLocation());
        vShader->SetIsPixelShader(false);

        vShader->SetNumConsts(plGraphVS::kNumConsts);
        
        vShader->SetVector(plGraphVS::kNumericConsts, 0, 0.5f, 1.f, 2.f);
        vShader->SetVector(plGraphVS::kPiConsts, 1.f / (2.f*hsScalarPI), hsScalarPI/2.f, hsScalarPI, hsScalarPI*2.f);
        vShader->SetVector(plGraphVS::kCosConsts, 1.f, -1.f/2.f, 1.f/24.f, -1.f/720.f);

#ifndef TEST_UVWS
        vShader->SetInputFormat(0);
#else // TEST_UVWS
        vShader->SetInputFormat(1);
#endif // TEST_UVWS
        vShader->SetOutputFormat(0);

//      vShader->SetShaderFileName("sha/vs_WaveGraph.inl");
//      vShader->SetShaderFileName("sha/vs_WaveGraph2.inl");
        vShader->SetDecl(plShaderTable::Decl(vs_WaveGraph2));

        hsgResMgr::ResMgr()->SendRef(vShader->GetKey(), TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnRequest, iPass, kRefGraphVShader), plRefFlags::kActiveRef);

        hsAssert(fGraphVShader[iPass] == vShader, "SendRef should have set shader");
    }

    IAddShaderToLayers(mat, 0, 2, plLayRefMsg::kVertexShader, fGraphVShader[iPass]);
}

void plWaveSet7::IAddGraphPShader(hsGMaterial* mat, int iPass)
{
    if( !fGraphPShader[iPass] )
    {
        plShader* pShader = TRACKED_NEW plShader;
        char buff[256];
        sprintf(buff, "%s_GraphPS_%d", GetKey()->GetName(), iPass);
        hsgResMgr::ResMgr()->NewKey(buff, pShader, GetKey()->GetUoid().GetLocation());
        pShader->SetIsPixelShader(true);
        
        pShader->SetNumConsts(plGraphPS::kNumConsts);

        pShader->SetInputFormat(0);
        pShader->SetOutputFormat(0);

//      pShader->SetShaderFileName("sha/ps_WaveGraph.inl");
        pShader->SetDecl(plShaderTable::Decl(ps_WaveGraph));

        hsgResMgr::ResMgr()->SendRef(pShader->GetKey(), TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnRequest, iPass, kRefGraphPShader), plRefFlags::kActiveRef);

        hsAssert(fGraphPShader[iPass] == pShader, "SendRef should have set shader");
    }

    IAddShaderToLayers(mat, 0, 2, plLayRefMsg::kPixelShader, fGraphPShader[iPass]);
}

plRenderTarget* plWaveSet7::ISetupGraphShoreRenderReq(int which)
{
    plConst(int) kGraphSize(256);
    char name[256];
    sprintf(name, "Graph_%d", which);
    plRenderTarget* rt = ICreateTransferRenderTarget(name, kGraphSize);
    
    hsgResMgr::ResMgr()->SendRef(rt->GetKey(), TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnRequest, which, kRefGraphShoreRT), plRefFlags::kActiveRef);

    fGraphReq[which] = ICreateRenderRequest(rt, fGraphShoreDraw[which], -100.f);
    fGraphReqMsg[which] = TRACKED_NEW plRenderRequestMsg(GetKey(), fGraphReq[which]);

    return rt;
}

void plWaveSet7::ISetupGraphShore(hsGMaterial* mat)
{
    if( !fGraphShoreRT[0] )
    {
        // Create the material
        ICreateGraphShoreMaterials();

        int i;
        for( i = 0; i < kGraphShorePasses; i++ )
        {
            // Create the mesh
            plDrawableSpans* drawable = ICreateEmptyGraphDrawable("GraphShore", kRefGraphShoreDraw, i);
            plConst(int) kGraphWidth(256);
            fGraphShoreDraw[i] = ICreateGraphDrawable(drawable, fGraphShoreMat[i], kGraphWidth);

            // Setup render requests
            // Return value is the texture we want to use for the shore line.
            fGraphShoreRT[i] = ISetupGraphShoreRenderReq(i);
        }

    }

    // If we've already done this material, we're done (it may be shared
    // with another shore mesh).
    if( fShoreVShader && mat->GetLayer(0) && (fShoreVShader == mat->GetLayer(0)->GetVertexShader()) )
        return;

    // We now have all our render target textures. Set them up as
    // our kGraphShorePasses layers on this material.
    ISetupShoreLayers(mat);

}

void plWaveSet7::IMakeShoreLayer(hsGMaterial* mat, int which)
{
    char name[512];
    if( which >= mat->GetNumLayers() )
    {
        plLayer* lay = TRACKED_NEW plLayer;
        sprintf(name, "%s_lay_%d", mat->GetKey()->GetName(), which);
        hsgResMgr::ResMgr()->NewKey(name, lay, GetKey()->GetUoid().GetLocation());

        lay->SetAmbientColor(hsColorRGBA().Set(0.f, 0.f, 0.f, 1.f));
        lay->SetRuntimeColor(hsColorRGBA().Set(1.f, 1.f, 1.f, 1.f));
        lay->SetOpacity(1.f);

        mat->AddLayerViaNotify(lay);
    }
}

void plWaveSet7::ISetupShoreLayers(hsGMaterial* mat)
{
    // Make sure we have exactly kGraphShorePasses layers on this mat.
    IMakeShoreLayer(mat, 0);
    IMakeShoreLayer(mat, 1);
    IMakeShoreLayer(mat, 2);

    // Make sure the state is correct for each layer.
    // And set the textures up to point at the render targets
    plLayer* lay = plLayer::ConvertNoRef(mat->GetLayer(0)->BottomOfStack());
    hsAssert(lay, "Bad first layer on material %s. Animated?");
//  lay->SetBlendFlags(hsGMatState::kBlendAlpha | hsGMatState::kBlendInvertFinalAlpha | hsGMatState::kBlendAlphaAlways);
    lay->SetBlendFlags(hsGMatState::kBlendAlpha);
    lay->SetClampFlags(hsGMatState::kClampTextureV);
    lay->SetShadeFlags(hsGMatState::kShadeNoProjectors
        | hsGMatState::kShadeNoShade
        | hsGMatState::kShadeWhite);
    lay->SetZFlags(hsGMatState::kZNoZWrite | hsGMatState::kZIncLayer);
    lay->SetMiscFlags(hsGMatState::kMiscTwoSided);
    ISetAsTexture(lay, fGraphShoreRT[0]);

    lay->SetUVWSrc(0);

    lay = plLayer::ConvertNoRef(mat->GetLayer(1));
    hsAssert(lay, "Bad second layer on material %s. Animated?");
    lay->SetBlendFlags(hsGMatState::kBlendAlpha);
    lay->SetClampFlags(hsGMatState::kClampTextureV);
    lay->SetShadeFlags(hsGMatState::kShadeNoProjectors
        | hsGMatState::kShadeNoShade
        | hsGMatState::kShadeWhite);
    lay->SetZFlags(hsGMatState::kZNoZWrite);
    lay->SetMiscFlags(hsGMatState::kMiscTwoSided);
    ISetAsTexture(lay, fGraphShoreRT[1]);

    lay->SetUVWSrc(1);

    lay = plLayer::ConvertNoRef(mat->GetLayer(2));
    hsAssert(lay, "Bad third layer on material %s. Animated?");
    lay->SetBlendFlags(hsGMatState::kBlendAlpha);
    lay->SetClampFlags(hsGMatState::kClampTextureV);
    lay->SetShadeFlags(hsGMatState::kShadeNoProjectors
        | hsGMatState::kShadeNoShade
        | hsGMatState::kShadeWhite);
    lay->SetZFlags(hsGMatState::kZNoZWrite);
    lay->SetMiscFlags(hsGMatState::kMiscTwoSided);
    ISetAsTexture(lay, fGraphShoreRT[2]);

    lay->SetUVWSrc(2);

    // Add the vertex and pixel shaders.
    IAddShoreVertexShader(mat);
    IAddShorePixelShader(mat);
}

void plWaveSet7::IInitGraph(int iPass)
{
    GraphState& gs = fGraphState[iPass];
    plShader* shader = fGraphVShader[iPass];

    // First the easy stuff.

    // Age starts off 0.
    gs.fAge = 0;

    static int lastOne = 0;

    plConst(hsScalar) kBasePeriod(3.f);
    hsScalar life = State().fPeriod * kBasePeriod * (1.f + fRand.RandZeroToOne()); 
    gs.fInvLife = (1.f + hsScalar(lastOne)/hsScalar(kGraphShorePasses-1)) / life;

    lastOne = !lastOne;

    gs.fUOff = fRand.RandZeroToOne();

    // Now the rest we have to think about a little, and
    // think about four times.
    int i;
    for( i = 0; i < 4; i++ )
    {
        // Okay, phase we don't have to think too hard about,
        // it doesn't matter as long as it's random.
        gs.fPhase[i] = fRand.RandZeroToOne() * 2.f * hsScalarPI;

        // Next up is frequency, but frequency is the hard one.
        // Remember frequency has to preserve tiling, so freq = k * 2 * PI.
        // We'd like to keep at least one big one around all the time,
        // so we'll always put a big one in first, and then a bunch of
        // smaller ones to noise it up nice.
        int k;
        if( !i )
        {
            k = fRand.RandZeroToOne() > 0.5 ? 1 : 2;
        }
        else
        {
            plConst(int) kMinFreq(3);
            plConst(int) kMaxFreq(10);

            k = kMinFreq + int((kMaxFreq-kMinFreq) * fRand.RandZeroToOne());
        }

        // Input will be in range [0..2], so we'll omit the customary 2*PI here.
        gs.fFreq[i] = k * hsScalarPI; 

        // Amplitude depends on freqency, or roughly inversely proportional
        // to frequency (randomized about linear on period).
        // Divide by 4 because that's how many oscillators we have, and they
        // are summed.
        hsScalar period = 1.f / hsScalar(k);
        plConst(hsScalar) kAmpScale(1.f / 4.f / 2.f);
        plConst(hsScalar) kMinPeriodFrac(1.f);
        plConst(hsScalar) kMaxPeriodFrac(2.f);
        period *= kMinPeriodFrac + fRand.RandZeroToOne() * (kMaxPeriodFrac - kMinPeriodFrac);
        period *= kAmpScale;
        gs.fAmp[i] = period;
    }
    // Go ahead and set the ones on the shader that won't be updated.
    shader->SetVector(plGraphVS::kPhase,
        gs.fPhase[0],
        gs.fPhase[1],
        gs.fPhase[2],
        gs.fPhase[3]);
    shader->SetVector(plGraphVS::kFrequency,
        gs.fFreq[0],
        gs.fFreq[1],
        gs.fFreq[2],
        gs.fFreq[3]);

    // Propagate all this to the shader.
    IUpdateGraphShader(0, iPass);
}

void plWaveSet7::IShuffleDownGraphs(int iPass)
{
    int i;
    for( i = iPass+1; i < kGraphShorePasses; i++ )
    {
        fGraphState[i-1] = fGraphState[i];
        fGraphVShader[i-1]->CopyConsts(fGraphVShader[i]);
    }
    IInitGraph(kGraphShorePasses-1);
}

void plWaveSet7::IUpdateGraphShader(hsScalar dt, int iPass)
{
    if( fGraphShoreDraw[iPass] )
    {
        GraphState& gs = fGraphState[iPass];
        plShader* shader = fGraphVShader[iPass];

        gs.fAge += dt;
        hsScalar rads = gs.fAge * gs.fInvLife;
        if( rads >= hsScalarPI )
        {
            // Recycle this one and restart the upper.
            IShuffleDownGraphs(iPass);
        }
        else
        {
            hsScalar sinAge = hsFastMath::SinInRange(rads);

            shader->SetVector(plGraphVS::kAmplitude, 
                gs.fAmp[0] * sinAge, 
                gs.fAmp[1] * sinAge, 
                gs.fAmp[2] * sinAge, 
                gs.fAmp[3] * sinAge);

            // Might want to tint this sometime.
            plConst(hsColorRGBA) kTint(hsColorRGBA().Set(1.f, 1.f, 1.f, 1.f));
            hsColorRGBA tint = kTint;
            tint.a *= sinAge;
            shader->SetColor(plGraphVS::kColor, tint);

            plConst(hsScalar) kCMax(1.f);
            plConst(hsScalar) kCMin(3.f);
            hsScalar cMin = kCMax + (kCMin - kCMax) * State().fFingerLength;
            plConst(hsScalar) k2ndLayerScale(2.f);
            plConst(hsScalar) k2ndLayerVoff(1.5f);
            shader->SetVector(plGraphVS::kUVWConsts,
                (kCMax - cMin) * sinAge + cMin,
                gs.fUOff,
                k2ndLayerVoff,
                k2ndLayerScale);
        }
    }
}

void plWaveSet7::IUpdateGraphShaders(plPipeline* pipe, hsScalar dt)
{
    if( fGraphShoreDraw[0] )
    {
        int i;
        for( i = kGraphShorePasses-1; i >= 0; i-- )
        {
            IUpdateGraphShader(dt, i);
        }
    }
}