/*==LICENSE==*

CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011  Cyan Worlds, Inc.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

Additional permissions under GNU GPL version 3 section 7

If you modify this Program, or any covered work, by linking or
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK,
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK
(or a modified version of those libraries),
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA,
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the
licensors of this Program grant you additional
permission to convey the resulting work. Corresponding Source for a
non-source form of such a combination shall include the source code for
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered
work.

You can contact Cyan Worlds, Inc. by email legal@cyan.com
 or by snail mail at:
      Cyan Worlds, Inc.
      14617 N Newport Hwy
      Mead, WA   99021

*==LICENSE==*/

#include "HeadSpin.h"
#include "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 "pnSceneObject/plSimulationInterface.h"

#include "plPhysical.h"

#include "plSurface/hsGMaterial.h"
#include "plDrawableSpans.h"
#include "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 "plMessage/plDampMsg.h"
#include "plMessage/plImpulseMsg.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 constexpr float kGravConst = 30.f;

static constexpr float FreqToLen(float f) { return hsConstants::two_pi<float> / f; }
static constexpr float LenToFreq(float l) { return hsConstants::two_pi<float> / l; }

static constexpr float MPH2FPS(float f) { return f * 5280.f / 3600.f; }
static constexpr float FPS2MPH(float f) { return f / 5280.f * 3600.f; }

plCONST(float) kTimeClamp(0.3f);

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

    dist *= fFreq;
    dist += fPhase;

    float 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(float len) const
{
    if( fStatusGraph )
    {
        float maxLen = TexState().fMaxLength * kCompositeSize / State().fRippleScale;
        int32_t val = int32_t(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 = nullptr;
}

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 = nullptr;
}
#else // PLASMA_EXTERNAL_RELEASE
inline void plWaveSet7::GraphLen(float len) const
{
}

inline void plWaveSet7::IRestartGraph() const
{
}

void plWaveSet7::StartGraph()
{
}

void plWaveSet7::StopGraph()
{
}

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(),
    fCurrTime(),
    fScrunchLen(8.f),
    fScrunchScale(2.f),
    fFreqScale(1.f),
    fRipVShader(),
    fRipPShader(),
    fShoreVShader(),
    fShorePShader(),
    fFixedVShader(),
    fFixedPShader(),
    fBiasVShader(),
    fBiasPShader(),
    fBumpMat(),
    fBumpDraw(),
    fBumpReq(),
    fBumpReqMsg(),
    fEnvMap(),
    fRefObj(),
    fCosineLUT(),
    fGraphShoreTex(),
    fBubbleShoreTex(),
    fEdgeShoreTex(),
    fMaxLen(),

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

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

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

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

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

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->ReadLEFloat();

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

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

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

    if (HasFlag(kHasBuoys))
    {
        n = stream->ReadLE32();
        for (i = 0; i < n; i++)
        {
            mgr->ReadKeyNotifyMe(stream, new plGenRefMsg(GetKey(), plRefMsg::kOnCreate, -1, kRefBuoy), 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->WriteLEFloat(fMaxLen);

    fState.Write(stream);

    stream->WriteLE32((uint32_t)fShores.size());
    for (plSceneObject* shore : fShores)
        mgr->WriteKey(stream, shore);

    stream->WriteLE32((uint32_t)fDecals.size());
    for (plSceneObject* decal : fDecals)
        mgr->WriteKey(stream, decal);

    mgr->WriteKey(stream, fEnvMap);

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

    if (HasFlag(kHasBuoys))
    {
        stream->WriteLE32((uint32_t)fBuoys.size());
        for (plSceneObject* buoy : fBuoys)
            mgr->WriteKey(stream, buoy);
    }
}

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

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

                fTrialUpdate &= ~kReRenderEnvMap;
            }
        }

        float 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.
        float dt = fLastTime > 0 ? float(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);
}

bool plWaveSet7::IAnyBoundsVisible(plPipeline* pipe) const
{
    for (const hsBounds3Ext& bounds : fTargBnds)
    {
        if (pipe->TestVisibleWorld(bounds))
            return true;
    }
    return false;
}

bool 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.emplace_back(plSceneObject::ConvertNoRef(refMsg->GetRef()));
        return true;
    case kRefDecal:
        fDecals.emplace_back(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 (std::find(fDecalMgrs.cbegin(), fDecalMgrs.cend(), dyna) == fDecalMgrs.cend())
            {
                fDecalMgrs.emplace_back(dyna);
            }
        }
        return true;
    case kRefBuoy:
        {
            plSceneObject* so = plSceneObject::ConvertNoRef(refMsg->GetRef());
            if (std::find(fBuoys.cbegin(), fBuoys.cend(), so) == fBuoys.cend())
            {
                IShiftCenter(so);
                fBuoys.emplace_back(so);
            }
        }
        return true;
    }
    return false;
}

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

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

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

void plWaveSet7::IUpdateWaves(float dt)
{
    ITransition(dt);
    ITransTex(dt);
    ICalcScale();

    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.
bool plWaveSet7::ITransContinue(float dt)
{
    float 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(float dt)
{
    // select the next wave for transitioning
    if( ++fTransistor >= kNumWaves )
        fTransistor = 0;

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

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

void plWaveSet7::ITransition(float 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);
    }

}

bool plWaveSet7::ITransTexContinue(float dt)
{
    float 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(float dt)
{
    if( ++fTexTrans >= kNumTexWaves )
        fTexTrans = 0;

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

void plWaveSet7::ITransTex(float dt)
{

    // If we're in a transition, keep with it.
    if( fTexTransDel != 0 )
    {
        plConst(float) 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(float dt, int i)
{
    plWorldWave7& wave = fWorldWaves[i];

    float len = FreqToLen(wave.fFreq);

    float speed = hsFastMath::InvSqrtAppr(len / (hsConstants::two_pi<float> * kGravConst));

    static float speedHack = 1.f;
    speed *= speedHack;
    wave.fPhase += speed * dt;
//  wave.fPhase = fmod( speed * t, hsConstants::two_pi<float>);

    float amp = GeoState().fAmpOverLen * len / float(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);

    float 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(float) kMinRotDeg(15.f);
    plConst(float) kMaxRotDeg(180.f);
    hsVector3 dir = fWindDir;

    float rotBase = GeoState().fAngleDev;

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

    float x = dir.fX;
    float 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);
}

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

    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
    float t = hsVector3(&accumPos, &pos).InnerProduct(accumNorm);
    t /= accumNorm.fZ;

    pos.fZ += t;

    norm = accumNorm;

    return pos.fZ;
}

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

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

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

        fState.fWaterHeight = h;

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

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

    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);
    float surfDepth = surfNorm.InnerProduct(surfPos);
    float 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.
    float area = (wBnd.GetMaxs().fX - wBnd.GetMins().fX) * (wBnd.GetMaxs().fY - wBnd.GetMins().fY);

    float volume = area * depth;

    plCONST(float) kWaterDensity(1.0f);
    float 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 = new plImpulseMsg(GetKey(), physKey, hsVector3(0.f, 0.f, 1.f) * forceMag * dt);
    iMsg->Send();

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

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

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

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

void plWaveSet7::IFloatBuoys(float dt)
{
    for (plSceneObject* buoy : fBuoys)
    {
        if (buoy && buoy->GetSimulationInterface() && buoy->GetSimulationInterface()->GetPhysical() && buoy->GetDrawInterface())
        {
            IFloatBuoy(dt, buoy);
        }
    }
}

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 = new plShiftMassMsg(GetKey(), so->GetSimulationInterface()->GetPhysical()->GetKey(), offset);
//      msg->Send();
    }
}

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

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

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

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

            targBnd.Union(&span.GetWorldBounds());
        }

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

        for (size_t 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.empty())
            fTargBnds.resize(1);
        
        plConst(float) kMaxWaveHeight(5.f);

        hsPoint3 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 = new plObjRefMsg(key, plRefMsg::kOnRequest, -1, plObjRefMsg::kModifier);
    hsgResMgr::ResMgr()->AddViaNotify( GetKey(), refMsg, plRefFlags::kActiveRef);
}

void plWaveSet7::IRemoveTarget(const plKey& key)
{
    plObjRefMsg* refMsg = 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 != nullptr);

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

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

void plWaveSet7::RemoveBuoy(const plKey& soKey)
{
    plGenRefMsg* msg = 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;
        float        **fMask;

    public:

        plFilterMask( float sig );
        virtual ~plFilterMask();

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

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

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

    plConst(float) kGeoMinLen(3.f);
    plConst(float) kGeoMaxLen(8.f);
    plConst(float) kGeoAmpOverLen(0.1f);
    plConst(float) kGeoAngleDev(hsDegreesToRadians(30.f));
    plConst(float) kGeoChop(1.f);

    plConst(float) kTexMinLen(4.f);
    plConst(float) kTexMaxLen(30.f);
    plConst(float) kTexAmpOverLen(0.1f);
    plConst(float) kTexAngleDev(hsDegreesToRadians(30.f));
    plConst(float) kTexChop(1.f);

    plFixedWaterState7 state;

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

    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(float) kNoise(1.f);
    plConst(float) kSpecStart(50.f);
    plConst(float) 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();
    state.fEnvRadius = 500.f;
    state.fEnvRefresh = 0.f;

    fState = state;

    IUpdateWindDir(0);
}


void plWaveSet7::IInitWaveConsts()
{
    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 (size_t 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)
{
    float rads = fRand.RandMinusOneToOne() * TexState().fAngleDev;
    float dx = sin(rads);
    float dy = cos(rads);


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

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

    float reps = float(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;

    float effK = hsFastMath::InvSqrt(dx*dx + dy*dy);
    fTexWaves[i].fLen = float(kCompositeSize) * effK;
    fTexWaves[i].fFreq = hsConstants::two_pi<float> / 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(plKey key)
{
    fSceneNode = std::move(key);
}

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

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

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

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

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

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

    ISubmitRenderRequests();
}

void plWaveSet7::IUpdateBumpLayers(float dt)
{
    plCONST(float) speedHack(1.f / 3.f);
    int i;
    for( i = 0; i < kNumTexWaves; i++ )
    {
        float speed = hsFastMath::InvSqrtAppr(fTexWaves[i].fLen / (hsConstants::two_pi<float> * 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);
        }

        Log("{.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(float amp, float dx, float dy) const
{
    return nullptr;
}

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 nullptr;
}

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

    ST::string buff = ST::format("{}_BiasBitPS", GetKey()->GetName());
    hsgResMgr::ResMgr()->NewKey(buff, mipMap, GetKey()->GetUoid().GetLocation());

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

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

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

            uint32_t* 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 = new plMipmap(
        kCompositeSize, sizeV,
        plMipmap::kARGB32Config,
        kNumLevels, 
        plMipmap::kUncompressed,
        plMipmap::UncompressedInfo::kRGB8888);

        ST::string buff = ST::format("{}_BumpBitPS", GetKey()->GetName());
        hsgResMgr::ResMgr()->NewKey(buff, mipMap, GetKey()->GetUoid().GetLocation());

        hsgResMgr::ResMgr()->SendRef(mipMap->GetKey(), 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++ )
        {
            float dist = float(i) / float(sizeU-1) * hsConstants::two_pi<float>;
            float c = cos(dist);
            float s = sin(dist);
            s *= 0.5f;
            s += 0.5f;
            s = float(pow(s, TexState().fChop));
            c *= s;
            uint8_t cosDist = uint8_t((c * 0.5 + 0.5) * 255.999f);
            int j;
            for( j = 0; j < sizeV; j++ )
            {
                uint32_t* 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 = new plLayer;
            ST::string buff = ST::format("{}_Bias_{}", GetKey()->GetName(), 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 ? hsGMatState::hsGMatMiscFlags(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 = 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 = new plLayer;
    ST::string buff = ST::format("{}_BumpLayerPS_{}", GetKey()->GetName(), 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 = 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 = new hsGMaterial;
    ST::string buff = ST::format("{}_BumpMatPS", GetKey()->GetName());
    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++ )
    {
        size_t 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(), new plGenRefMsg(GetKey(), plRefMsg::kOnRequest, 0, kRefBumpMat), plRefFlags::kActiveRef);

    ICreateBumpDrawable();

    return fBumpMat = bumpMat;
}

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

        ST::string buff = ST::format("{}_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;
        float biasScale = 0.5f * specVec[State().kNoise] / (float(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(), new plGenRefMsg(GetKey(), plRefMsg::kOnRequest, 0, kRefBiasVShader), plRefFlags::kActiveRef);


        fBiasVShader = vShader;
    }

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


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

        ST::string buff = ST::format("{}_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(), new plGenRefMsg(GetKey(), plRefMsg::kOnRequest, 0, kRefBiasPShader), plRefFlags::kActiveRef);

        fBiasPShader = pShader;
    }

    refMsg = 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 = new plShader;
            ST::string buff = ST::format("{}_BumpVS_{}", 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(), 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 = new plShader;
            ST::string buff = ST::format("{}_BumpPS_{}", 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 / float(kBumpPerPass)),
                    -fTexWaves[iBase + iLay].fDirY * (1.f / float(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(), 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 = new plDrawableSpans;
    ST::string buff = ST::format("{}_BumpDraw", GetKey()->GetName());
    hsgResMgr::ResMgr()->NewKey(buff, fBumpDraw, GetKey()->GetUoid().GetLocation());

    ICreateClearDrawable(fBumpDraw, fBumpMat);
    
    hsgResMgr::ResMgr()->SendRef(fBumpDraw->GetKey(), 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_t 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,
                                                        nullptr, false, nullptr,
                                                        6, idx, 
                                                        mat, 
                                                        hsMatrix44::IdentityMatrix(), 
                                                        false,
                                                        nullptr,
                                                        drawable);

    return drawable;
}

plRenderRequest* plWaveSet7::ICreateRenderRequest(plRenderTarget* rt, plDrawableSpans* draw, float pri)
{
    plRenderRequest* req = 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_t flags = plRenderTarget::kIsTexture | plRenderTarget::kIsOrtho;
    uint8_t bitDepth = 32;
    uint8_t zDepth = 0;
    uint8_t stencilDepth = 0;
    
    plRenderTarget* rt = new plRenderTarget(flags, size, size, bitDepth, zDepth, stencilDepth);

    ST::string buff = ST::format("{}_{}", 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()) : nullptr;
    if( !layer )
    {
        layer = new plLayer;
        ST::string buff = ST::format("{}_{}LayerPS_{}", 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 = 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 = new plLayer;
    ST::string buff = ST::format("{}_{}_EnvLayerPS_{}", GetKey()->GetName(), pref, 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 = 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 
    for (hsSsize_t i = mat->GetNumLayers()-1; i > 0; i--)
    {
        plMatRefMsg* refMsg = new plMatRefMsg(mat->GetKey(), plRefMsg::kOnRemove, (int32_t)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 = 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 = new plDynamicEnvMap((uint16_t)fEnvSize, (uint16_t)fEnvSize, 32);
        hsgResMgr::ResMgr()->NewKey(GetKey()->GetName(), env, GetKey()->GetUoid().GetLocation());
        fEnvMap = env;
        env->SetPosition(hsPoint3(0.f, 0.f, 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 = new plShader;

        ST::string buff = ST::format("{}_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*hsConstants::pi<float>*4.f*4.f),
                                                 hsConstants::half_pi<float>,
                                                 hsConstants::pi<float>,
                                                 hsConstants::two_pi<float>);
        vShader->SetVector(plShoreVS::kNumericConsts, 0, 0.5f, 1.f, 2.f);

        plConst(float) kK1(0.5f);
        plConst(float) kK2(1.5f);
        float 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(), 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 = new plShader;

        ST::string buff = ST::format("{}_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(), 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 = new plShader;

        ST::string buff = ST::format("{}_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*hsConstants::pi<float>*4.f*4.f),
                                                  hsConstants::half_pi<float>,
                                                  hsConstants::pi<float>,
                                                  hsConstants::two_pi<float>);
        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(), 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_t type, plShader* shader)
{
    if( iFirst < 0 )
        iFirst = 0;
    if( uint32_t(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 = 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 = new plShader;
        ST::string buff = ST::format("{}_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(), 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 = new plShader;
        ST::string buff = ST::format("{}_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*hsConstants::pi<float>*4.f*4.f),
                                               hsConstants::half_pi<float>,
                                               hsConstants::pi<float>,
                                               hsConstants::two_pi<float>);
        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(float) kRipBias(0.1f);
        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(), 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 = new plShader;
        ST::string buff = ST::format("{}_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(), 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 = new plShader;
        ST::string buff = ST::format("{}_{}", 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*hsConstants::pi<float>*4.f*4.f),
                                                   hsConstants::half_pi<float>,
                                                   hsConstants::pi<float>,
                                                   hsConstants::two_pi<float>);
        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(float) kBias(0.1f);
        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(), 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 nullptr;
        }
        break;
    default:
        hsAssert(false, "Only 1 or 2 layers currently supported");
    }
    return nullptr;
}

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 = new plShader;

        ST::string buff = ST::format("{}_{}", 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(), 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 nullptr;

    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 nullptr;
    }

    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_t)      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;
            float scale = 1.f / (float(kNumBumpShaders) + specVec[State().kNoise]);

            float maxLen = TexState().fMaxLength * kCompositeSize / State().fRippleScale;

            float 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);

            float layScale = skip & (1 << iTex) ? 0.f : (1.f / float(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 float IRound(float f)
{
    return float(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.
        float dt = fLastTime > 0 ? float(fCurrTime - fLastTime) : hsTimer::GetDelSysSeconds();
        plConst(float) kRate(-0.1f);
        float tx = kRate * dt;
        float ty = kRate * dt;
        plConst(float) kScaleU(4.f);
        plConst(float) kScaleV(1.f);
        tx += fBiasVShader->GetFloat(plBiasVS::kTexU0, 3);
        tx -= float(int(tx));

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

        float scale = 1.f + (4.f - 1.f) * TexState().fAngleDev/hsConstants::pi<float>;

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

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

        plConst(float) kUpperNoiseOffU(0.f);
        plConst(float) 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;
        float biasScale = 0.5f * specVec[State().kNoise] / (float(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 / (hsConstants::two_pi<float> * 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);

            float envRadius = State().fEnvRadius;

            hsVector3 camToCen(&envCenter, &worldCam);
            float 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 / (hsConstants::two_pi<float> * 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(float) kK1(2.f);
        plConst(float) kK2(5.f);
        float 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 / (hsConstants::two_pi<float> * 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(float) kEnvRadius(500.f);
        float envRadius = State().fEnvRadius;

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

        hsPoint3 envCenter(State().fEnvCenter);

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

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

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

        float specAtten = State().fTexState.fAmpOverLen * hsConstants::two_pi<float>;

        plCONST(float) kScaleHack(0.1f);
        float baseScale = kScaleHack;
//      baseScale *= float(kBumpPerPass) * (float(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 *= (float(kNumBumpShaders) + specVec[State().kNoise]);
        baseScale *= (TexState().fChop + 1.f);


        float specStart = specVec[State().kSpecStart];
        float 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 / (hsConstants::two_pi<float> * 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()
{
    for (plSceneObject* shore : fShores)
        ICheckShoreMaterial(shore);
}

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

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

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

    if (src.empty())
        return;

    for (const plAccessSpan& span : src)
        ISetupGraphShore(span.GetMaterial());

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

}

void plWaveSet7::ICheckDecalMaterials()
{
    for (plSceneObject* decal : fDecals)
        ICheckDecalMaterial(decal);
}

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

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

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

    if (src.empty())
        return;

    for (const plAccessSpan& span : src)
        ISetupDecal(span.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 = nullptr;

        plMatRefMsg* refMsg;

        for (hsSsize_t i = mat->GetNumLayers() - 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 = new plMatRefMsg(mat->GetKey(), plRefMsg::kOnRequest, (int32_t)(i+1), plMatRefMsg::kLayer | plMatRefMsg::kInsert);
                hsgResMgr::ResMgr()->SendRef(lay3->GetKey(), refMsg, plRefFlags::kActiveRef);
            }

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

            refMsg = new plMatRefMsg(mat->GetKey(), plRefMsg::kOnRequest, (int32_t)(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);

    for (size_t 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, new plGenRefMsg(GetKey(), plRefMsg::kOnCreate, -1, kRefShore), plRefFlags::kPassiveRef);

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

//////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////
bool 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;

    for (size_t 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;

    std::vector<hsPoint3> pos(nVerts);
    std::vector<hsVector3> norm(nVerts);

#ifdef TEST_UVWS
    std::vector<hsPoint3> uvw(nVerts); // Are we actually ever going to use these uvws?
#endif // TEST_UVWS

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

        float delX = float(i) / float(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;

    std::vector<uint16_t> idxArr(nTris * 3);

    uint16_t* idx = idxArr.data();

    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.data(), norm.data(),
#ifndef TEST_UVWS
                                                        nullptr, 0,
#else // TEST_UVWS
                                                        uvw.data(), 1,
#endif // TEST_UVWS
                                                        nullptr, false, nullptr,
                                                        nTris * 3, idxArr.data(),
                                                        mat, 
                                                        hsMatrix44::IdentityMatrix(), 
                                                        false,
                                                        nullptr,
                                                        drawable);

    return drawable;
}

plDrawableSpans* plWaveSet7::ICreateEmptyGraphDrawable(const char* name, uint32_t ref, int which)
{
    plDrawableSpans* drawable = new plDrawableSpans;
    ST::string buff = ST::format("{}_{}_{}", GetKey()->GetName(), name, which);
    hsgResMgr::ResMgr()->NewKey(buff, drawable, GetKey()->GetUoid().GetLocation());

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

    return drawable;
}

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

    ST::string buff = ST::format("{}_{}_{}", GetKey()->GetName(), name, which);
    hsgResMgr::ResMgr()->NewKey(buff, mat, GetKey()->GetUoid().GetLocation());

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

    return mat;
}

plLayer* plWaveSet7::ICreateBlankLayer(const char* name, int suff)
{
    plLayer* lay = new plLayer;
    ST::string buff = ST::format("{}_{}_{}", 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_t ref)
{
    plMipmap* mipMap = new plMipmap(
        width, height,
        plMipmap::kARGB32Config,
        1, 
        plMipmap::kUncompressed,
        plMipmap::UncompressedInfo::kRGB8888);

    ST::string buff = ST::format("{}_{}", GetKey()->GetName(), name);
    hsgResMgr::ResMgr()->NewKey(buff, mipMap, GetKey()->GetUoid().GetLocation());

    hsgResMgr::ResMgr()->SendRef(mipMap->GetKey(), new plGenRefMsg(GetKey(), plRefMsg::kOnRequest, 0, (int8_t)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(float) kRampFrac(0.4f);
        plConst(float) 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_t 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_t color = (alpha << 24)
                    | (0xff << 16)
                    | (0xff << 8)
                    | 0xff;

            int i;
            for( i = 0; i < width; i++ )
            {
                uint32_t* 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_t));

    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(float) kMinRad(2.f);
        plConst(float) kMaxRad(5.0f);
        int radius = int(kMinRad + fRand.RandZeroToOne() * (kMaxRad - kMinRad));
        float invRadiusSq = 1.f / float(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;

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

                uint32_t* val = mipMap->GetAddr32(ii, jj);
                uint32_t alpha = (*val) >> 24;
                alpha = uint32_t(float(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_t* val = mipMap->GetAddr32(i, j);
            hsColorRGBA col;
            col.FromARGB32(*val);
            float 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 )
    {
        (void)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(float) kCenter(0.8f);
    plConst(float) 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 float invRadiusSq = 1.f / float(radius*radius);

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

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

            alpha = uint32_t(a * maxAlpha);
        }

        int i;
        for( i = 0; i < width; i++ )
        {
            uint32_t* 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 )
    {
        (void)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 = 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()
{
    for (size_t 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, size_t iPass)
{
    if (!fGraphVShader[iPass]) {
        plShader* vShader = new plShader;
        ST::string buff = ST::format("{}_GraphVS_{}", 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 / hsConstants::two_pi<float>,
                                                 hsConstants::half_pi<float>,
                                                 hsConstants::pi<float>,
                                                 hsConstants::two_pi<float>);
        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(), 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, size_t iPass)
{
    if (!fGraphPShader[iPass]) {
        plShader* pShader = new plShader;
        ST::string buff = ST::format("{}_GraphPS_{}", 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(), 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(), new plGenRefMsg(GetKey(), plRefMsg::kOnRequest, which, kRefGraphShoreRT), plRefFlags::kActiveRef);

    fGraphReq[which] = ICreateRenderRequest(rt, fGraphShoreDraw[which], -100.f);
    fGraphReqMsg[which] = 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)
{
    if( which >= mat->GetNumLayers() )
    {
        plLayer* lay = new plLayer;
        ST::string name = ST::format("{}_lay_{}", 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(size_t 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(float) kBasePeriod(3.f);
    float life = State().fPeriod * kBasePeriod * (1.f + fRand.RandZeroToOne()); 
    gs.fInvLife = (1.f + float(lastOne)/float(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() * hsConstants::two_pi<float>;

        // 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 * hsConstants::pi<float>;

        // 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.
        float period = 1.f / float(k);
        plConst(float) kAmpScale(1.f / 4.f / 2.f);
        plConst(float) kMinPeriodFrac(1.f);
        plConst(float) 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(size_t iPass)
{
    for (size_t i = iPass+1; i < kGraphShorePasses; i++) {
        fGraphState[i-1] = fGraphState[i];
        fGraphVShader[i-1]->CopyConsts(fGraphVShader[i]);
    }
    IInitGraph(kGraphShorePasses-1);
}

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

        gs.fAge += dt;
        float rads = gs.fAge * gs.fInvLife;
        if (rads >= hsConstants::pi<float>) {
            // Recycle this one and restart the upper.
            IShuffleDownGraphs(iPass);
        } else {
            float 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(float) kCMax(1.f);
            plConst(float) kCMin(3.f);
            float cMin = kCMax + (kCMin - kCMax) * State().fFingerLength;
            plConst(float) k2ndLayerScale(2.f);
            plConst(float) k2ndLayerVoff(1.5f);
            shader->SetVector(plGraphVS::kUVWConsts,
                (kCMax - cMin) * sinAge + cMin,
                gs.fUOff,
                k2ndLayerVoff,
                k2ndLayerScale);
        }
    }
}

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