/*==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 "plDynaDecalMgr.h"
#include "plDynaDecal.h"

#include "plCutter.h"

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

#include "plDrawableSpans.h"
#include "plAuxSpan.h"
#include "plSpaceTree.h"

#include "plPrintShape.h"

#include "plAvatar/plArmatureMod.h"

#include "plParticleSystem/plParticleSystem.h"
#include "plParticleSystem/plParticleEmitter.h"

#include "pnSceneObject/plSceneObject.h"
#include "pnSceneObject/plDrawInterface.h"
#include "plSurface/hsGMaterial.h"
#include "plSurface/plLayerInterface.h"
#include "plScene/plPageTreeMgr.h"

#include "plPipeline/plGBufferGroup.h"
#include "plPipeline/hsGDeviceRef.h"

#include "plMessage/plAgeLoadedMsg.h"
#include "plMessage/plDynaDecalEnableMsg.h"
#include "pnMessage/plRefMsg.h"
#include "pnMessage/plTimeMsg.h"
#include "plgDispatch.h"

#include "pnEncryption/plRandom.h"
#include "hsFastMath.h"

#include "hsStream.h"
#include "hsResMgr.h"
#include "hsTimer.h"

#include "pnMessage/plPipeResMakeMsg.h"

#include "plGImage/plBumpMapGen.h"

// Stuff for creating a bumpenv decal on demand.
#include "plGImage/plMipmap.h"
#include "plSurface/plLayer.h"
#include "plMessage/plLayRefMsg.h"

//### Hackage
#include "plMessage/plRenderMsg.h"
#include "plMessage/plListenerMsg.h"
#include "plPipeline.h"

#include "plTweak.h"

#include "plProfile.h"

plProfile_CreateTimerNoReset("Total", "DynaDecal", Total);
plProfile_CreateTimerNoReset("Cutter", "DynaDecal", Cutter);
plProfile_CreateTimerNoReset("Process", "DynaDecal", Process);
plProfile_CreateTimerNoReset("Callback", "DynaDecal", Callback);

static plRandom sRand;
static const int    kBinBlockSize = 20;
static const uint16_t kDefMaxNumVerts = 1000;
static const uint16_t kDefMaxNumIdx = kDefMaxNumVerts;

static const float kDefLifeSpan = 30.f;
static const float kDefDecayStart = kDefLifeSpan * 0.5f;
static const float kDefRampEnd = kDefLifeSpan * 0.1f;

static const float kInitAuxSpans = 5;

#define MF_NO_INIT_ALLOC
#define MF_NEVER_RUN_OUT

// If we aren't doing an initial alloc, we MUST have NEVER_RUN_OUT
// It's also useful to not have initial alloc but do have NEVER_RUN_OUT
// So:
//  MF_NO_INIT_ALLOC && MF_NEVER_RUN_OUT - Okay
//  !MF_NO_INIT_ALLOC && !MF_NEVER_RUN_OUT - Okay
//  !MF_NO_INIT_ALLOC && MF_NEVER_RUN_OUT - Okay
//  MF_NO_INIT_ALLOC && !MF_NEVER_RUN_OUT - Bad (you'll never get any decals)
#if defined(MF_NO_INIT_ALLOC) && !defined(MF_NEVER_RUN_OUT)
#define MF_NEVER_RUN_OUT
#endif // defined(MF_NO_INIT_ALLOC) && !defined(MF_NEVER_RUN_OUT)

using namespace std;

hsBool plDynaDecalMgr::fDisableAccumulate = false;
hsBool plDynaDecalMgr::fDisableUpdate = false;

plDynaDecalMgr::plDynaDecalMgr()
:   
    fMatPreShade(nil),
    fMatRTShade(nil),
    fMaxNumVerts(kDefMaxNumVerts),
    fMaxNumIdx(kDefMaxNumIdx),
    fWetLength(0),
    fRampEnd(kDefRampEnd),
    fDecayStart(kDefDecayStart),
    fLifeSpan(kDefLifeSpan),
    fInitAtten(1.f),
    fIntensity(1.f),
    fWaitOnEnable(false),
    fGridSizeU(2.5f),
    fGridSizeV(2.5f),
    fScale(1.f, 1.f, 1.f),
    fPartyTime(1.f)
{
    fCutter = new plCutter;
}

plDynaDecalMgr::~plDynaDecalMgr()
{
    int i;
    for( i = 0; i < fDecals.GetCount(); i++ )
        delete fDecals[i];

    for( i = 0; i < fAuxSpans.GetCount(); i++ )
    {
        if( fAuxSpans[i]->fDrawable )
        {
            plSpan* span = const_cast<plSpan*>(fAuxSpans[i]->fDrawable->GetSpan(fAuxSpans[i]->fBaseSpanIdx));

            span->RemoveAuxSpan(fAuxSpans[i]);
        }

        delete fAuxSpans[i]->fGroup;

        delete fAuxSpans[i];
    }

    delete fCutter;
}

void plDynaDecalMgr::SetKey(plKey k)
{
    hsKeyedObject::SetKey(k);
    if( k )
    {
        plgDispatch::Dispatch()->RegisterForExactType(plEvalMsg::Index(), GetKey());
        plgDispatch::Dispatch()->RegisterForExactType(plPipeGeoMakeMsg::Index(), GetKey());
        plgDispatch::Dispatch()->RegisterForExactType(plAgeLoadedMsg::Index(), GetKey());
    }
}

void plDynaDecalMgr::Read(hsStream* stream, hsResMgr* mgr)
{
    plSynchedObject::Read(stream, mgr);

    mgr->ReadKeyNotifyMe(stream, new plGenRefMsg(GetKey(), plRefMsg::kOnCreate, 0, kRefMatPreShade), plRefFlags::kActiveRef);

    mgr->ReadKeyNotifyMe(stream, new plGenRefMsg(GetKey(), plRefMsg::kOnCreate, 0, kRefMatRTShade), plRefFlags::kActiveRef);

    int n = stream->ReadLE32();
    int i;
    for( i = 0; i < n; i++ )
    {
        mgr->ReadKeyNotifyMe(stream, new plGenRefMsg(GetKey(), plRefMsg::kOnCreate, 0, kRefTarget), plRefFlags::kPassiveRef);
    }
    // Associated slave particle systems. We read in the scene objects now, and find the associated systems on loaded message.
    n = stream->ReadLE32();
    for( i = 0; i < n; i++ )
    {
        mgr->ReadKeyNotifyMe(stream, new plGenRefMsg(GetKey(), plRefMsg::kOnCreate, 0, kRefPartyObject), plRefFlags::kPassiveRef);
    }

    fMaxNumVerts = (uint16_t)(stream->ReadLE32());
    fMaxNumIdx = (uint16_t)(stream->ReadLE32());

    fWaitOnEnable = stream->ReadLE32();

    fIntensity = stream->ReadLEScalar();
    fInitAtten = fIntensity;

    fWetLength = stream->ReadLEScalar();
    fRampEnd = stream->ReadLEScalar();
    fDecayStart = stream->ReadLEScalar();
    fLifeSpan = stream->ReadLEScalar();

    fGridSizeU = stream->ReadLEScalar();
    fGridSizeV = stream->ReadLEScalar();

    fScale.Read(stream);

    fPartyTime = stream->ReadLEScalar();

    n = stream->ReadLE32();
    fNotifies.SetCount(n);
    for( i = 0; i < n; i++ )
        fNotifies[i] = mgr->ReadKey(stream);
    
    // If we need to be creating DynaDecalMgrs on the fly, this should go in the
    // constructor, or we should call it explicitly on the DynaDecalMgr we create.
    // But putting it here makes it automatic for normal scene loading, without
    // popping up during the export conversion process.
#ifndef MF_NO_INIT_ALLOC
    InitAuxSpans();
#endif // MF_NO_INIT_ALLOC

    /////////////////////////////////////////////////////
    // ###Things that should be in derived classes follow.

}

void plDynaDecalMgr::Write(hsStream* stream, hsResMgr* mgr)
{
    plSynchedObject::Write(stream, mgr);

    mgr->WriteKey(stream, fMatPreShade);
    mgr->WriteKey(stream, fMatRTShade);

    stream->WriteLE32(fTargets.GetCount());

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

    // Particle systems (really their associated sceneobjects).
    stream->WriteLE32(fPartyObjects.GetCount());
    for( i = 0; i < fPartyObjects.GetCount(); i++ )
    {
        mgr->WriteKey(stream, fPartyObjects[i]);
    }

    stream->WriteLE32(fMaxNumVerts);
    stream->WriteLE32(fMaxNumIdx);

    stream->WriteLE32(fWaitOnEnable);

    stream->WriteLEScalar(fIntensity);

    stream->WriteLEScalar(fWetLength);
    stream->WriteLEScalar(fRampEnd);
    stream->WriteLEScalar(fDecayStart);
    stream->WriteLEScalar(fLifeSpan);

    stream->WriteLEScalar(fGridSizeU);
    stream->WriteLEScalar(fGridSizeV);

    fScale.Write(stream);

    stream->WriteLEScalar(fPartyTime);

    stream->WriteLE32(fNotifies.GetCount());
    for( i = 0; i < fNotifies.GetCount(); i++ )
        mgr->WriteKey(stream, fNotifies[i]);

    /////////////////////////////////////////////////////
    // ###Things that should be in derived classes follow.

}

hsBool plDynaDecalMgr::IMakeAuxRefs(plPipeline* pipe)
{
    int i;
    for( i = 0; i < fGroups.GetCount(); i++ )
        fGroups[i]->PrepForRendering(pipe, false);

    return true;
}

const plPrintShape* plDynaDecalMgr::IGetPrintShape(const plKey& objKey) const
{
    const plPrintShape* shape = nil;
    plSceneObject* part = plSceneObject::ConvertNoRef(objKey->ObjectIsLoaded());
    if( part )
    {
        // This is a safe cast, because GetGenericInterface(type) will only return
        // either a valid object of that type, or nil.
        shape = static_cast<plPrintShape*>(part->GetGenericInterface(plPrintShape::Index()));
    }
    return shape;
}

const plPrintShape* plDynaDecalMgr::IGetPrintShape(plArmatureMod* avMod, uint32_t id) const
{
    const plPrintShape* shape = nil;
    const plSceneObject* part = avMod->FindBone(id);
    if( part )
    {
        // This is a safe cast, because GetGenericInterface(type) will only return
        // either a valid object of that type, or nil.
        shape = static_cast<plPrintShape*>(part->GetGenericInterface(plPrintShape::Index()));
    }
    return shape;
}

hsBool plDynaDecalMgr::IHandleEnableMsg(const plDynaDecalEnableMsg* enaMsg)
{
    IWetParts(enaMsg);

    return true;
}

hsBool plDynaDecalMgr::IWetParts(const plDynaDecalEnableMsg* enaMsg)
{
    if( !enaMsg->IsArmature() )
    {
        const plPrintShape* shape = IGetPrintShape(enaMsg->GetShapeKey());
        if( shape )
        {
            plDynaDecalInfo& info = IGetDecalInfo(uintptr_t(shape), shape->GetKey());
            IWetInfo(info, enaMsg);
        }
    }
    else
    if( enaMsg->GetID() == uint32_t(-1) )
    {
        plArmatureMod* avMod = plArmatureMod::ConvertNoRef(enaMsg->GetArmKey()->ObjectIsLoaded());
        int i;
        for( i = 0; i < fPartIDs.GetCount(); i++ )
        {
            const plPrintShape* shape = IGetPrintShape(avMod, fPartIDs[i]);
            if( shape )
            {
                plDynaDecalInfo& info = IGetDecalInfo(uintptr_t(shape), shape->GetKey());
                IWetInfo(info, enaMsg);
            }
        }
    }
    else
    {
        IWetPart(enaMsg->GetID(), enaMsg);
    }
    return true;
}

hsBool plDynaDecalMgr::IWetPart(uint32_t id, const plDynaDecalEnableMsg* enaMsg)
{
    plArmatureMod* avMod = plArmatureMod::ConvertNoRef(enaMsg->GetArmKey()->ObjectIsLoaded());

    const plPrintShape* shape = IGetPrintShape(avMod, id);
    if( shape )
    {
        plDynaDecalInfo& info = IGetDecalInfo(uintptr_t(shape), shape->GetKey());
        IWetInfo(info, enaMsg);
    }
    return true;
}

void plDynaDecalMgr::IWetInfo(plDynaDecalInfo& info, const plDynaDecalEnableMsg* enaMsg) const
{
    info.fWetTime = enaMsg->GetContactTime();
    info.fWetLength = enaMsg->GetWetLength();
    if( !enaMsg->AtEnd() )
        info.fFlags |= plDynaDecalInfo::kImmersed;
    else
        info.fFlags &= ~plDynaDecalInfo::kImmersed;
}

hsBool plDynaDecalMgr::MsgReceive(plMessage* msg)
{
    // On eval pulse, update all our active decals, letting old ones die off.
    plEvalMsg* eval = plEvalMsg::ConvertNoRef(msg);
    if( eval )
    {
        IUpdateDecals(hsTimer::GetSysSeconds());
        return true;
    }

    plDynaDecalEnableMsg* enaMsg = plDynaDecalEnableMsg::ConvertNoRef(msg);
    if( enaMsg )
    {
        IHandleEnableMsg(enaMsg);

        return true;
    }

    plPipeGeoMakeMsg* make = plPipeGeoMakeMsg::ConvertNoRef(msg);
    if( make )
    {
        return IMakeAuxRefs(make->Pipeline());
    }

    plAgeLoadedMsg* ageLoadMsg = plAgeLoadedMsg::ConvertNoRef(msg);
    if( ageLoadMsg && ageLoadMsg->fLoaded ) 
    {
        IGetParticles();
        return true;
    }

    plGenRefMsg* refMsg = plGenRefMsg::ConvertNoRef(msg);
    if( refMsg )
    {
        switch( refMsg->fType )
        {
        case kRefMatPreShade:
            if( refMsg->GetContext() & (plRefMsg::kOnCreate|plRefMsg::kOnRequest|plRefMsg::kOnReplace) )
                fMatPreShade = hsGMaterial::ConvertNoRef(refMsg->GetRef());
            else
                fMatPreShade = nil;
            return true;
        case kRefMatRTShade:
            if( refMsg->GetContext() & (plRefMsg::kOnCreate|plRefMsg::kOnRequest|plRefMsg::kOnReplace) )
                fMatRTShade = hsGMaterial::ConvertNoRef(refMsg->GetRef());
            else
                fMatRTShade = nil;
            return true;
        case kRefTarget:
            if( refMsg->GetContext() & (plRefMsg::kOnCreate|plRefMsg::kOnRequest|plRefMsg::kOnReplace) )
            {
                fTargets.Append(plSceneObject::ConvertNoRef(refMsg->GetRef()));
            }
            else
            {
                int idx = fTargets.Find((plSceneObject*)refMsg->GetRef());
                if( idx != fTargets.kMissingIndex )
                    fTargets.Remove(idx);
            }
            return true;
        case kRefPartyObject:
            if( refMsg->GetContext() & (plRefMsg::kOnCreate|plRefMsg::kOnRequest|plRefMsg::kOnReplace) )
            {
                fPartyObjects.Append(plSceneObject::ConvertNoRef(refMsg->GetRef()));
            }
            else
            {
                int idx = fPartyObjects.Find((plSceneObject*)refMsg->GetRef());
                if( idx != fPartyObjects.kMissingIndex )
                    fPartyObjects.Remove(idx);
            }
            return true;
        case kRefParticles:
            if( refMsg->GetContext() & (plRefMsg::kOnCreate|plRefMsg::kOnRequest|plRefMsg::kOnReplace) )
            {
                fParticles.Append(plParticleSystem::ConvertNoRef(refMsg->GetRef()));
                fParticles[fParticles.GetCount()-1]->fMiscFlags |= plParticleSystem::kParticleSystemAlwaysUpdate;
            }
            else
            {
                int idx = fParticles.Find((plParticleSystem*)refMsg->GetRef());
                if( idx != fParticles.kMissingIndex )
                    fParticles.Remove(idx);
            }
            return true;
        case kRefAvatar:
            if( refMsg->GetContext() & (plRefMsg::kOnRemove|plRefMsg::kOnDestroy) )
                IRemoveDecalInfo(uintptr_t(refMsg->GetRef()));
            return true;
        }
    }

    return plSynchedObject::MsgReceive(msg);
}

//////////////////////////////////////////////////////////////////////////////////
//
void plDynaDecalMgr::INotifyActive(plDynaDecalInfo& info, const plKey& armKey, uint32_t id) const
{
    if( !(info.fFlags & plDynaDecalInfo::kActive) )
    {
        double secs = hsTimer::GetSysSeconds();
        int i;
        for( i = 0; i < fNotifies.GetCount(); i++ )
        {
            plDynaDecalEnableMsg* enaMsg = new plDynaDecalEnableMsg(fNotifies[i], armKey, secs, fWetLength, false, id);
            enaMsg->Send();
        }
        info.fFlags |= plDynaDecalInfo::kActive;
    }
}

void plDynaDecalMgr::INotifyInactive(plDynaDecalInfo& info, const plKey& armKey, uint32_t id) const
{
    if( info.fFlags & plDynaDecalInfo::kActive )
    {
        double secs = hsTimer::GetSysSeconds();
        int i;
        for( i = 0; i < fNotifies.GetCount(); i++ )
        {
            plDynaDecalEnableMsg* enaMsg = new plDynaDecalEnableMsg(fNotifies[i], armKey, secs, fWetLength, true, id);
            enaMsg->Send();
        }
        info.fFlags &= ~plDynaDecalInfo::kActive;
    }
}

plDynaDecalInfo& plDynaDecalInfo::Init(const plKey& key)
{
    fKey = key;

    fLastTime = -1.e33f;
    fLastPos.Set(-1.e33f, -1.e33f, -1.e33f);
    fWetTime = -1.e33f;
    fWetLength = 0;
    fFlags = kNone;

    return *this;
}

plDynaDecalInfo& plDynaDecalMgr::IGetDecalInfo(uintptr_t id, const plKey& key)
{
    plDynaDecalMap::iterator iter = fDecalMap.find(id);
    if( iter == fDecalMap.end() )
    {
        plDynaDecalInfo decalInfo;
        decalInfo.Init(key);
        plGenRefMsg* refMsg = new plGenRefMsg(GetKey(), plRefMsg::kOnRequest, 0, kRefAvatar);
        hsgResMgr::ResMgr()->AddViaNotify(plKey(key), refMsg, plRefFlags::kPassiveRef);

        pair<plDynaDecalMap::iterator, bool> iterPair;
        iterPair = fDecalMap.insert(plDynaDecalMap::value_type(id, decalInfo));
        iter = iterPair.first;
    }

    return iter->second;
}

void plDynaDecalMgr::IRemoveDecalInfo(uint32_t id)
{
    plDynaDecalMap::iterator iter = fDecalMap.find(id);
    if( iter != fDecalMap.end() )
        fDecalMap.erase(iter, iter);
}

void plDynaDecalMgr::IRemoveDecalInfos(const plKey& key)
{
    plDynaDecalMap::iterator iter = fDecalMap.begin();
    while( iter != fDecalMap.end() )
    {
        if( iter->second.fKey == key )
        {
            plDynaDecalMap::iterator nuke0 = iter;
            plDynaDecalMap::iterator nuke1 = iter;
            iter++;
            while( (iter != fDecalMap.end()) && (iter->second.fKey == key) )
            {
                nuke1 = iter;
                iter++;
            }
            fDecalMap.erase(nuke0, nuke1);
        }
    }
}

float plDynaDecalMgr::IHowWet(plDynaDecalInfo& info, double t) const
{
    // We aren't playing this wet/dry/enable/disable thing.
    if( !fWaitOnEnable )
        return fIntensity;

    // We've been notified we've entered the pool, 
    // and haven't been notified we've left it.
    if( info.fFlags & plDynaDecalInfo::kImmersed )
    {
        info.fWetTime = t;
        return fIntensity;
    }

    // We've never been enabled.
    if( info.fWetLength <= 0 )
        return 0;

    // We're wet, let's see how wet.
    float wet = (float)(1.f - (t - info.fWetTime) / info.fWetLength);
    if( wet > 1.f ) // This should never happen. It means t < info.fWetTime (we get wet in the future).
        return fIntensity;
    if( wet < 0 )
        return 0;
    return wet * fIntensity;
}
//
//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////

plAuxSpan* plDynaDecalMgr::IGetAuxSpan(plDrawableSpans* targ, int iSpan, hsGMaterial* mat, uint16_t numVerts, uint16_t numIdx)
{
    // Some of this code just assumes you get the number of verts you ask for.
    // Which was causing errors when you asked for more than the max and didn't
    // get it. So now if you ask for too many verts, you just lose.
    if (numVerts > fMaxNumVerts || numIdx > fMaxNumIdx)
        return nil;

    plSpan* span = const_cast<plSpan*>(targ->GetSpan(iSpan));

    int i;

    // First, see if we've got an aux span already sitting on this span.
    // We can use an existing aux span iff
    // a) we own it
    // b) there's enough room to append our stuff.
    //
    // This is kind of overkill in both respects, because:
    // a) we don't care if we own it, it just really needs the same material
    // b) there might be room at the beginning or middle of the span
    //
    // Relaxing those brings on the additional bookkeeping with having a span
    // that requires multiple drawprimitive calls.
    // Case a) can cause it if we share with a DynaDecalMgr with a different
    // lifespan than ours. That would allow decals in the middle of the span
    // to die out.
    // Case b) won't have random bits dropping out of the middle, but will 
    // create a gap in the middle (and probably end) when we wrap around.
    //
    // The simplicity in bookkeeping should make up for any space/speed advantages
    // in packing more into a single AuxSpan.
    for( i = 0; i < span->GetNumAuxSpans(); i++ )
    {
        plAuxSpan* aux = span->GetAuxSpan(i);
        if( (aux->fOwner == (void*)this)
            &&(aux->fVStartIdx + aux->fVLength + numVerts < aux->fVBufferLimit)
            &&(aux->fIStartIdx + aux->fILength + numIdx < aux->fIBufferLimit) )
            return aux;
    }

    hsBool rtLit = span->fProps & plSpan::kLiteVtxNonPreshaded;

    // Now look to see if we've got one sitting around unused that's suitable.
    // Here the suitable criteria is a little different. We know we are the owner,
    // and we know there's enough room (because it's sitting idle).
    for( i = 0; i < fAuxSpans.GetCount(); i++ )
    {
        plAuxSpan* aux = fAuxSpans[i];
        if( !aux->fDrawable
            &&(aux->fVStartIdx + aux->fVLength + numVerts < aux->fVBufferLimit)
            &&(aux->fIStartIdx + aux->fILength + numIdx < aux->fIBufferLimit) )
        {
            aux->fDrawable = targ;
            aux->fBaseSpanIdx = iSpan;

            ISetAuxMaterial(aux, mat, rtLit);

            span->AddAuxSpan(aux);

            return aux;
        }
    }

    // Ain't got one. We could allocate another one, or we can just say too bad doo-dad.
    // If we allocate a new one:
    //      A) we'll need to flush managed memory to load it in.
    //      B) we'll be stuck with that memory (video and system) used up until this age is paged out and reloaded
    //      C) we've got a whole bunch of extra faces to draw.
    // If we just return nil:
    //      A) opposite of above
    //      B) we stop leaving footprints/ripples for a while.
    // I'm going to try the latter for a bit.

#ifdef MF_NEVER_RUN_OUT
    // Okay, nothing there. Let's get a new one.
    plAuxSpan* aux = new plAuxSpan;
    fAuxSpans.Append(aux);

    IAllocAuxSpan(aux, numVerts, numIdx);

    aux->fOwner = (void*)this;
    aux->fDrawable = targ;
    aux->fBaseSpanIdx = iSpan;

    ISetAuxMaterial(aux, mat, rtLit);

    span->AddAuxSpan(aux);

    return aux;
#else // MF_NEVER_RUN_OUT
    return nil;
#endif // MF_NEVER_RUN_OUT
}

void plDynaDecalMgr::InitAuxSpans()
{
    int i;
    for( i = 0; i < kInitAuxSpans; i++ )
    {
        plAuxSpan* aux = new plAuxSpan;
        fAuxSpans.Append(aux);
        IAllocAuxSpan(aux, fMaxNumVerts, fMaxNumIdx);
    }
}

void plDynaDecalMgr::IAllocAuxSpan(plAuxSpan* aux, uint32_t maxNumVerts, uint32_t maxNumIdx)
{
    int iGrp = fGroups.GetCount();
    plGBufferGroup* grp = new plGBufferGroup(kDecalVtxFormat, true, false);
    fGroups.Append(grp);

    grp->ReserveVertStorage(maxNumVerts, 
        &aux->fVBufferIdx, 
        &aux->fCellIdx, 
        &aux->fCellOffset, 
        plGBufferGroup::kReserveInterleaved);

    aux->fFlags = 0;

    aux->fVStartIdx = grp->GetVertStartFromCell(aux->fVBufferIdx, aux->fCellIdx, aux->fCellOffset);
    aux->fVLength = 0;

    uint16_t* dataPtr = nil;
    grp->ReserveIndexStorage(maxNumIdx, &aux->fIBufferIdx, &aux->fIStartIdx, &dataPtr);
    aux->fIStartIdx /* should be assigning something? */;

    aux->fILength = 0;

    aux->fGroup = grp;

    aux->fVBufferInit = aux->fVStartIdx;
    aux->fVBufferLimit = aux->fVBufferInit + maxNumVerts;

    aux->fIBufferInit = aux->fIStartIdx;
    aux->fIBufferLimit = aux->fIBufferInit + maxNumIdx;

    aux->fOrigPos.SetCount(maxNumVerts);
    aux->fOrigUVW.SetCount(maxNumVerts);

    aux->fOwner = (void*)this;
    aux->fDrawable = nil;
    aux->fBaseSpanIdx = 0;

    grp->SetVertBufferStart(aux->fVBufferIdx, aux->fVStartIdx);
    grp->SetIndexBufferStart(aux->fIBufferIdx, aux->fIStartIdx);
    grp->SetVertBufferEnd(aux->fVBufferIdx, aux->fVStartIdx);
    grp->SetIndexBufferEnd(aux->fIBufferIdx, aux->fIStartIdx);
}

hsGMaterial* plDynaDecalMgr::ISetAuxMaterial(plAuxSpan* aux, hsGMaterial* mat, hsBool rtLit)
{
    if( !mat )
        mat = fMatRTShade;

    hsBool attenColor =  0 != (mat->GetLayer(0)->GetBlendFlags() 
                                & (hsGMatState::kBlendAdd
                                    | hsGMatState::kBlendMult
                                    | hsGMatState::kBlendMADD));
    hsBool bump = 0 != (mat->GetLayer(0)->GetMiscFlags() & hsGMatState::kMiscBumpChans);
    hsBool hasVS = nil != mat->GetLayer(0)->GetVertexShader();

    if( hasVS )
    {
        aux->fFlags |= plAuxSpan::kVertexShader;
        aux->fFlags &= ~plAuxSpan::kAttenColor;
        aux->fFlags &= ~(plAuxSpan::kOverrideLiteModel | plAuxSpan::kRTLit);
        aux->fMaterial = mat;
    }
    else
    if( bump )
    {
        aux->fFlags &= ~plAuxSpan::kVertexShader;
        aux->fFlags |= plAuxSpan::kAttenColor;
        aux->fFlags &= ~(plAuxSpan::kOverrideLiteModel | plAuxSpan::kRTLit);
        aux->fMaterial = mat;
    }
    else
    if( attenColor )
    {
        aux->fFlags &= ~plAuxSpan::kVertexShader;
        aux->fFlags |= plAuxSpan::kOverrideLiteModel | plAuxSpan::kAttenColor;
        aux->fFlags &= ~plAuxSpan::kRTLit;
        aux->fMaterial = mat;
    }
    else
    if( rtLit )
    {
        aux->fFlags &= ~plAuxSpan::kVertexShader;
        aux->fFlags &= ~(plAuxSpan::kOverrideLiteModel | plAuxSpan::kAttenColor);
        aux->fFlags |= plAuxSpan::kRTLit;
        aux->fMaterial = mat;
    }
    else
    {
        aux->fFlags &= ~(plAuxSpan::kOverrideLiteModel | plAuxSpan::kAttenColor);
        aux->fFlags &= ~plAuxSpan::kRTLit;
        aux->fMaterial = fMatPreShade;
    }
    return aux->fMaterial;
}

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

plDynaDecal* plDynaDecalMgr::IInitDecal(plAuxSpan* aux, double t, uint16_t numVerts, uint16_t numIdx)
{
    int idx = INewDecal();

    fDecals[idx]->fStartVtx = (uint16_t)(aux->fVStartIdx + aux->fVLength);
    fDecals[idx]->fNumVerts = numVerts;
    fDecals[idx]->fStartIdx = (uint16_t)(aux->fIStartIdx + aux->fILength);
    fDecals[idx]->fNumIdx = numIdx;

    fDecals[idx]->fBirth = t;
    fDecals[idx]->fFlags = plDynaDecal::kFresh;
    fDecals[idx]->fInitAtten = fInitAtten;

    fDecals[idx]->fAuxSpan = aux;

    aux->fVLength += numVerts;
    aux->fGroup->SetVertBufferEnd(aux->fVBufferIdx, aux->fVStartIdx + aux->fVLength);
    aux->fGroup->DirtyVertexBuffer(aux->fVBufferIdx);

    aux->fILength += numIdx;
    aux->fGroup->SetIndexBufferEnd(aux->fIBufferIdx, aux->fIStartIdx + aux->fILength);
    aux->fGroup->DirtyIndexBuffer(aux->fIBufferIdx);

    if( aux->fFlags & plAuxSpan::kVertexShader )
        fDecals[idx]->fFlags |= plDynaDecal::kVertexShader;
    else
    if( aux->fFlags & plAuxSpan::kAttenColor )
        fDecals[idx]->fFlags |= plDynaDecal::kAttenColor;

    // We should probably assert here that our span hasn't just overrun buffergroup storage.
    hsAssert(aux->fVStartIdx + aux->fVLength <= aux->fVBufferLimit, "Overrunning allocated storage");
    hsAssert(aux->fIStartIdx + aux->fILength <= aux->fIBufferLimit, "Overrunning allocated storage");

    hsAssert(aux->fGroup->GetVertBufferEnd(aux->fVBufferIdx) >= aux->fGroup->GetVertBufferStart(aux->fVBufferIdx), "Going out of range on verts");
    hsAssert(aux->fGroup->GetIndexBufferEnd(aux->fIBufferIdx) >= aux->fGroup->GetIndexBufferStart(aux->fIBufferIdx), "Going out of range on verts");

    return fDecals[idx];
}

void plDynaDecalMgr::IKillDecal(int i)
{
    // Update this decal's span.
    // Since decals die off in the same order they are created, and we always 
    // append a decal to a span, we only need to advance the span's start indices,
    // and decrement the lengths.
    plAuxSpan* aux = fDecals[i]->fAuxSpan;
    aux->fVStartIdx += fDecals[i]->fNumVerts;
    aux->fGroup->SetVertBufferStart(aux->fVBufferIdx, aux->fVStartIdx);
    aux->fVLength -= fDecals[i]->fNumVerts;

    aux->fIStartIdx += fDecals[i]->fNumIdx;
    aux->fGroup->SetIndexBufferStart(aux->fIBufferIdx, aux->fIStartIdx);
    aux->fILength -= fDecals[i]->fNumIdx;

    hsAssert(aux->fGroup->GetVertBufferEnd(aux->fVBufferIdx) >= aux->fGroup->GetVertBufferStart(aux->fVBufferIdx), "Going out of range on verts");
    hsAssert(aux->fGroup->GetIndexBufferEnd(aux->fIBufferIdx) >= aux->fGroup->GetIndexBufferStart(aux->fIBufferIdx), "Going out of range on verts");

    if( !aux->fVLength )
    {
        hsAssert(!aux->fILength, "Ran out of verts before indices");
        aux->fVStartIdx = aux->fVBufferInit;
        aux->fIStartIdx = aux->fIBufferInit;

        aux->fGroup->SetVertBufferStart(aux->fVBufferIdx, aux->fVStartIdx);
        aux->fGroup->SetIndexBufferStart(aux->fIBufferIdx, aux->fIStartIdx);
        aux->fGroup->SetVertBufferEnd(aux->fVBufferIdx, aux->fVStartIdx);
        aux->fGroup->SetIndexBufferEnd(aux->fIBufferIdx, aux->fIStartIdx);

        hsAssert(aux->fGroup->GetVertBufferEnd(aux->fVBufferIdx) >= aux->fGroup->GetVertBufferStart(aux->fVBufferIdx), "Going out of range on verts");
        hsAssert(aux->fGroup->GetIndexBufferEnd(aux->fIBufferIdx) >= aux->fGroup->GetIndexBufferStart(aux->fIBufferIdx), "Going out of range on verts");

        if( aux->fDrawable )
            const_cast<plSpan*>(aux->fDrawable->GetSpan(aux->fBaseSpanIdx))->RemoveAuxSpan(aux);

        aux->fDrawable = nil;
        aux->fBaseSpanIdx = 0;
    }

    delete fDecals[i];
    int newCount = fDecals.GetCount()-1;
    if( i < newCount )
    {
        memmove(&fDecals[i], &fDecals[i+1], (newCount-i) * sizeof(fDecals[i]));
    }
    fDecals.SetCount(newCount);
}

void plDynaDecalMgr::IUpdateDecals(double t)
{
    if( fDisableUpdate )
        return;

    int i;

    for( i = 0; i < fDecals.GetCount(); i++ )
    {
        if( fDecals[i]->Age(t, fRampEnd, fDecayStart, fLifeSpan) )
        {
            IKillDecal(i);
            i--;
        }
    }

    for( i = 0; i < fAuxSpans.GetCount(); i++ )
    {
        if( fAuxSpans[i]->fVLength )
        {
            plAuxSpan* aux = fAuxSpans[i];
            aux->fGroup->DirtyVertexBuffer(aux->fVBufferIdx);
        }
    }
}

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

void plDynaDecalMgr::ICountIncoming(hsTArray<plCutoutPoly>& src, uint16_t& numVerts, uint16_t& numIdx) const
{
    numVerts = 0;
    numIdx = 0;
    int j;
    for( j = 0; j < src.GetCount(); j++ )
    {
        if( src[j].fVerts.GetCount() )
        {
            numVerts += src[j].fVerts.GetCount();
            numIdx += src[j].fVerts.GetCount()-2;
        }
    }
    numIdx *= 3;
}

plDecalVtxFormat* plDynaDecalMgr::IGetBaseVtxPtr(const plAuxSpan* auxSpan) const
{
    plGBufferGroup* grp = auxSpan->fGroup;
    plGBufferCell* cell = grp->GetCell(auxSpan->fVBufferIdx, auxSpan->fCellIdx);

    uint8_t* ptr = grp->GetVertBufferData(auxSpan->fVBufferIdx);
    
    ptr += cell->fVtxStart + auxSpan->fCellOffset;

    return (plDecalVtxFormat*)ptr;

}

uint16_t* plDynaDecalMgr::IGetBaseIdxPtr(const plAuxSpan* auxSpan) const
{
    plGBufferGroup* grp = auxSpan->fGroup;

    return grp->GetIndexBufferData(auxSpan->fIBufferIdx) + auxSpan->fIBufferInit;
}

hsBool plDynaDecalMgr::IConvertFlatGrid(plAuxSpan* auxSpan,
                                        plDynaDecal* decal,
                                        const plFlatGridMesh& grid) const
{
    plDecalVtxFormat* vtx = IGetBaseVtxPtr(auxSpan);
    vtx += decal->fStartVtx;
    decal->fVtxBase = vtx;

    hsPoint3* origPos = &auxSpan->fOrigPos[decal->fStartVtx];
    hsPoint3* origUVW = &auxSpan->fOrigUVW[decal->fStartVtx];

    uint32_t initColor = decal->fFlags & plDynaDecal::kAttenColor
        ? 0xff000000
        : 0x00ffffff;
    int iv;
    for( iv = 0; iv < decal->fNumVerts; iv++ )
    {
        *origPos = vtx->fPos = grid.fVerts[iv].fPos;

        vtx->fNorm.Set(0.f, 0.f, 1.f);

        vtx->fDiffuse = initColor;
        vtx->fSpecular = 0;

        vtx->fUVW[0] = grid.fVerts[iv].fUVW;
        vtx->fUVW[1].Set(0.f, 0.f, 0.f);
        *origUVW = grid.fVerts[iv].fUVW;
        origUVW->fZ = 1.f;

        vtx++;
        origPos++;
        origUVW++;
    }

    uint16_t* idx = IGetBaseIdxPtr(auxSpan);
    idx += decal->fStartIdx;

    hsAssert(grid.fIdx.GetCount() == decal->fNumIdx, "Mismatch on dynamic indices");

    uint16_t base = decal->fStartVtx;
    int ii;
    for( ii = 0; ii < grid.fIdx.GetCount(); ii++ )
    {
        hsAssert(grid.fIdx[ii] + base - decal->fStartVtx < decal->fNumVerts, "Index going out of range");
        hsAssert(grid.fIdx[ii] + base < auxSpan->fIStartIdx + auxSpan->fILength, "Index going out of range.");

        *idx++ = grid.fIdx[ii] + base;
    }

    auxSpan->fGroup->DirtyVertexBuffer(auxSpan->fVBufferIdx);
    auxSpan->fGroup->DirtyIndexBuffer(auxSpan->fIBufferIdx);

    return true;
}

void plDynaDecalMgr::ISetDepthFalloff()
{
    const float totalDepth = fCutter->GetLengthW();

    // Currently all constants, but these could be set per DecalMgr.
    plConst(float) kMinFeet(3.f);
    plConst(float) kMaxFeet(10.f);

    plConst(float) kMinDepth(0.25f);
    plConst(float) kMaxDepth(0.75f);

    fMinDepth = kMinFeet / totalDepth;
    if( fMinDepth > kMinDepth )
        fMinDepth = kMinDepth;

    fMinDepthRange = 1.f / fMinDepth;

    fMaxDepth = 1.f - (kMaxFeet / totalDepth);
    if( fMaxDepth < kMaxDepth )
        fMaxDepth = kMaxDepth;

    fMaxDepthRange = 1.f / (1.f - fMaxDepth);
}

hsBool plDynaDecalMgr::IConvertPolys(plAuxSpan* auxSpan,
                                   plDynaDecal* decal, 
                                   hsTArray<plCutoutPoly>& src)
{
    ISetDepthFalloff();

    if( decal->fFlags & plDynaDecal::kVertexShader )
        return IConvertPolysVS(auxSpan, decal, src);

    if( decal->fFlags & plDynaDecal::kAttenColor )
        return IConvertPolysColor(auxSpan, decal, src);

    return IConvertPolysAlpha(auxSpan, decal, src);
}

hsBool plDynaDecalMgr::IConvertPolysAlpha(plAuxSpan* auxSpan,
                                   plDynaDecal* decal, 
                                   hsTArray<plCutoutPoly>& src)
{
    hsBool loU = false;
    hsBool hiU = false;
    hsBool loV = false;
    hsBool hiV = false;
    plDecalVtxFormat* vtx = IGetBaseVtxPtr(auxSpan);
    vtx += decal->fStartVtx;
    decal->fVtxBase = vtx;

    hsPoint3* origPos = &auxSpan->fOrigPos[decal->fStartVtx];
    hsPoint3* origUVW = &auxSpan->fOrigUVW[decal->fStartVtx];

    const hsVector3 backDir = fCutter->GetBackDir();

    int iPoly = 0;
    int iVert = 0;
    int iv;
    for( iv = 0; iv < decal->fNumVerts; iv++ )
    {
        *origPos = vtx->fPos = src[iPoly].fVerts[iVert].fPos;

        vtx->fNorm = src[iPoly].fVerts[iVert].fNorm;
        vtx->fUVW[0] = src[iPoly].fVerts[iVert].fUVW;

        if( vtx->fUVW[0].fX < 0.5f )
            loU = true;
        else 
            hiU = true;
        if( vtx->fUVW[0].fY < 0.5f )
            loV = true;
        else
            hiV = true;

        hsColorRGBA col = src[iPoly].fVerts[iVert].fColor;

        float depth = vtx->fUVW[0].fZ;

        float opac = depth < fMinDepth
            ? depth * fMinDepthRange
            : depth > fMaxDepth
                ? (1.f - depth) * fMaxDepthRange
                : 1.f;

        float normOpac = 1.f - vtx->fNorm.InnerProduct(backDir);
        opac *= 1.f - normOpac * normOpac;
        if( opac < 0 )
            opac = 0;

        if( src[iPoly].fBaseHasAlpha )
            opac *= col.a;
        col.a = 0;

        origUVW->fX = vtx->fUVW[0].fX;
        origUVW->fY = vtx->fUVW[0].fY;

        origUVW->fZ = opac;
        vtx->fUVW[1].Set(0, 0, 0);

        vtx->fDiffuse = col.ToARGB32();
        vtx->fSpecular = 0;


        if( ++iVert >= src[iPoly].fVerts.GetCount() )
        {
            iVert = 0;
            iPoly++;
        }
        vtx++;
        origPos++;
        origUVW++;
    }
    hsAssert(vtx <= IGetBaseVtxPtr(auxSpan) + auxSpan->fVBufferLimit, "Vtx pointer gone wild");

    uint16_t* idx = IGetBaseIdxPtr(auxSpan);
    idx += decal->fStartIdx;

    uint16_t base = decal->fStartVtx;
    int j;
    for( j = 0; j < src.GetCount(); j++ )
    {
        uint16_t next = base+1;
        int k;
        for( k = 2; k < src[j].fVerts.GetCount(); k++ )
        {
            *idx++ = base;
            *idx++ = next++;
            *idx++ = next;
        }
        base = ++next;
    }
    hsAssert(idx <= auxSpan->fGroup->GetIndexBufferData(auxSpan->fIBufferIdx) + auxSpan->fIBufferLimit, "Index ptr gone wild");

    auxSpan->fGroup->DirtyVertexBuffer(auxSpan->fVBufferIdx);
    auxSpan->fGroup->DirtyIndexBuffer(auxSpan->fIBufferIdx);

    return loU & hiU & loV & hiV;
}

hsBool plDynaDecalMgr::IConvertPolysColor(plAuxSpan* auxSpan,
                                   plDynaDecal* decal, 
                                   hsTArray<plCutoutPoly>& src)
{
    hsBool loU = false;
    hsBool hiU = false;
    hsBool loV = false;
    hsBool hiV = false;
    plDecalVtxFormat* vtx = IGetBaseVtxPtr(auxSpan);
    vtx += decal->fStartVtx;
    decal->fVtxBase = vtx;

    hsPoint3* origPos = &auxSpan->fOrigPos[decal->fStartVtx];
    hsPoint3* origUVW = &auxSpan->fOrigUVW[decal->fStartVtx];

    const hsVector3 backDir = fCutter->GetBackDir();
    int iPoly = 0;
    int iVert = 0;
    int iv;
    for( iv = 0; iv < decal->fNumVerts; iv++ )
    {
        *origPos = vtx->fPos = src[iPoly].fVerts[iVert].fPos;

        vtx->fNorm = src[iPoly].fVerts[iVert].fNorm;
        vtx->fUVW[0] = src[iPoly].fVerts[iVert].fUVW;

        if( vtx->fUVW[0].fX < 0.5f )
            loU = true;
        else 
            hiU = true;
        if( vtx->fUVW[0].fY < 0.5f )
            loV = true;
        else
            hiV = true;

        float depth = vtx->fUVW[0].fZ;
        float opac = depth < fMinDepth
            ? depth * fMinDepthRange
            : depth > fMaxDepth
                ? (1.f - depth) * fMaxDepthRange
                : 1.f;

        float normOpac = 1.f - vtx->fNorm.InnerProduct(backDir);
        opac *= 1.f - normOpac * normOpac;
        if( opac < 0 )
            opac = 0;

        origUVW->fX = vtx->fUVW[0].fX;
        origUVW->fY = vtx->fUVW[0].fY;

        origUVW->fZ = opac;
        vtx->fUVW[1].Set(0, 0, 0);

        vtx->fDiffuse = 0xff000000;
        vtx->fSpecular = 0;


        if( ++iVert >= src[iPoly].fVerts.GetCount() )
        {
            iVert = 0;
            iPoly++;
        }
        vtx++;
        origPos++;
        origUVW++;
    }
    hsAssert(vtx <= IGetBaseVtxPtr(auxSpan) + auxSpan->fVBufferLimit, "Vtx pointer gone wild");

    uint16_t* idx = IGetBaseIdxPtr(auxSpan);
    idx += decal->fStartIdx;

    uint16_t base = decal->fStartVtx;
    int j;
    for( j = 0; j < src.GetCount(); j++ )
    {
        uint16_t next = base+1;
        int k;
        for( k = 2; k < src[j].fVerts.GetCount(); k++ )
        {
            *idx++ = base;
            *idx++ = next++;
            *idx++ = next;
        }
        base = ++next;
    }
    hsAssert(idx <= auxSpan->fGroup->GetIndexBufferData(auxSpan->fIBufferIdx) + auxSpan->fIBufferLimit, "Index ptr gone wild");

    auxSpan->fGroup->DirtyVertexBuffer(auxSpan->fVBufferIdx);
    auxSpan->fGroup->DirtyIndexBuffer(auxSpan->fIBufferIdx);

    return loU & hiU & loV & hiV;
}

hsBool plDynaDecalMgr::IConvertPolysVS(plAuxSpan* auxSpan,
                                   plDynaDecal* decal, 
                                   hsTArray<plCutoutPoly>& src)
{
    hsBool loU = false;
    hsBool hiU = false;
    hsBool loV = false;
    hsBool hiV = false;
    plDecalVtxFormat* vtx = IGetBaseVtxPtr(auxSpan);
    vtx += decal->fStartVtx;
    decal->fVtxBase = vtx;

    hsPoint3* origPos = &auxSpan->fOrigPos[decal->fStartVtx];
    hsPoint3* origUVW = &auxSpan->fOrigUVW[decal->fStartVtx];

    int iPoly = 0;
    int iVert = 0;
    int iv;
    for( iv = 0; iv < decal->fNumVerts; iv++ )
    {
        *origPos = vtx->fPos = src[iPoly].fVerts[iVert].fPos;

        vtx->fNorm = src[iPoly].fVerts[iVert].fNorm;
        vtx->fUVW[0] = src[iPoly].fVerts[iVert].fUVW;

        if( vtx->fUVW[0].fX < 0.5f )
            loU = true;
        else 
            hiU = true;
        if( vtx->fUVW[0].fY < 0.5f )
            loV = true;
        else
            hiV = true;

        origUVW->fX = vtx->fUVW[0].fX;
        origUVW->fY = vtx->fUVW[0].fY;

        origUVW->fZ = vtx->fUVW[0].fZ = (float)decal->fBirth;

        vtx->fUVW[1].Set(0, 0, 0);

        const hsColorRGBA& col = src[iPoly].fVerts[iVert].fColor;
        vtx->fDiffuse = col.ToARGB32();
        vtx->fSpecular = 0;


        if( ++iVert >= src[iPoly].fVerts.GetCount() )
        {
            iVert = 0;
            iPoly++;
        }
        vtx++;
        origPos++;
        origUVW++;
    }
    hsAssert(vtx <= IGetBaseVtxPtr(auxSpan) + auxSpan->fVBufferLimit, "Vtx pointer gone wild");

    uint16_t* idx = IGetBaseIdxPtr(auxSpan);
    idx += decal->fStartIdx;

    uint16_t base = decal->fStartVtx;
    int j;
    for( j = 0; j < src.GetCount(); j++ )
    {
        uint16_t next = base+1;
        int k;
        for( k = 2; k < src[j].fVerts.GetCount(); k++ )
        {
            *idx++ = base;
            *idx++ = next++;
            *idx++ = next;
        }
        base = ++next;
    }
    hsAssert(idx <= auxSpan->fGroup->GetIndexBufferData(auxSpan->fIBufferIdx) + auxSpan->fIBufferLimit, "Index ptr gone wild");

    auxSpan->fGroup->DirtyVertexBuffer(auxSpan->fVBufferIdx);
    auxSpan->fGroup->DirtyIndexBuffer(auxSpan->fIBufferIdx);

    return loU & hiU & loV & hiV;
}

hsBool plDynaDecalMgr::IHitTestPolys(hsTArray<plCutoutPoly>& src) const
{
    hsBool loU = false;
    hsBool hiU = false;
    hsBool loV = false;
    hsBool hiV = false;
    int iPoly = 0;
    int iVert = 0;
    while( iPoly < src.GetCount() )
    {
        const hsPoint3& uvw = src[iPoly].fVerts[iVert].fUVW;

        if( uvw.fX < 0.5f )
            loU = true;
        else 
            hiU = true;
        if( uvw.fY < 0.5f )
            loV = true;
        else
            hiV = true;

        if( ++iVert >= src[iPoly].fVerts.GetCount() )
        {
            iVert = 0;
            iPoly++;
        }
    }

    return loU & hiU & loV & hiV;
}

hsBool plDynaDecalMgr::IProcessPolys(plDrawableSpans* targ, int iSpan, double t, hsTArray<plCutoutPoly>& src)
{
    // Figure out how many verts and idxs are coming in.
    uint16_t numVerts, numIdx;
    ICountIncoming(src, numVerts, numIdx);
    if( !numVerts )
        return false;

    // Find a span to put them in. Either the current span, or a new
    // one if it's full up.
    plAuxSpan* auxSpan = IGetAuxSpan(targ, iSpan, nil, numVerts, numIdx);

    // If we're full up, just see if we hit anything, but don't 
    // make any more decals. Might be nice to accelerate decay
    // here, since we definitely aren't keeping up.
    if( !auxSpan )
        return IHitTestPolys(src);

    // Get a decal to manage this group's aging.
    // Update the span to point to enough room.
    plDynaDecal* decal = IInitDecal(auxSpan, t, numVerts, numIdx);

    // Convert the polys from src into the accessor tris
    return IConvertPolys(auxSpan, decal, src);
}

hsBool plDynaDecalMgr::IProcessGrid(plDrawableSpans* targ, int iSpan, hsGMaterial* mat, double t, const plFlatGridMesh& grid)
{
    // Find a span to put them in. Either the current span, or a new
    // one if it's full up.
    plAuxSpan* auxSpan = IGetAuxSpan(targ, iSpan, mat, grid.fVerts.GetCount(), grid.fIdx.GetCount());

    // If we're full up, just see if we hit anything, but don't 
    // make any more decals.
    if( !auxSpan )
        return IHitTestFlatGrid(grid);

    auxSpan->fFlags |= plAuxSpan::kWorldSpace;

    // Get a decal to manage this group's aging.
    // Update the span to point to enough room.
    plDynaDecal* decal = IInitDecal(auxSpan, t, grid.fVerts.GetCount(), grid.fIdx.GetCount());

    // Convert the grid from src into the accessor tris
    return IConvertFlatGrid(auxSpan, decal, grid);
}

hsBool plDynaDecalMgr::IHitTestFlatGrid(const plFlatGridMesh& grid) const
{
    return true;
}

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

hsBool plDynaDecalMgr::ICutoutGrid(plDrawableSpans* drawable, int iSpan, hsGMaterial* mat, double secs)
{
    static plFlatGridMesh grid;
    grid.Reset();

    int nWid = int(fCutter->GetLengthU() / fGridSizeU);
    int nLen = int(fCutter->GetLengthV() / fGridSizeV);

    fCutter->CutoutGrid(nWid, nLen, grid);

    return IProcessGrid(drawable, iSpan, mat, secs, grid);
}

hsBool plDynaDecalMgr::ICutoutObject(plSceneObject* so, double secs)
{
    if( fDisableAccumulate )
        return false;

    hsBool retVal = false;

    if( !so )
        return retVal;

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

    plProfile_BeginTiming(Total);
    int numGot = 0;
    int j;
    for( j = 0; j < di->GetNumDrawables(); j++ )
    {
        plDrawableSpans* dr = plDrawableSpans::ConvertNoRef(di->GetDrawable(j));
        // Nil dr - it hasn't loaded yet or something.
        if( dr )
        {
            plDISpanIndex& diIndex = dr->GetDISpans(di->GetDrawableMeshIndex(j));
            if( !diIndex.IsMatrixOnly() )
            {
                int k;
                for( k = 0; k < diIndex.GetCount(); k++ )
                {
                    const plSpan* span = dr->GetSpan(diIndex[k]);
                    if( kVolumeCulled != fCutter->GetIsect().Test(span->fWorldBounds) )
                    {
                        plAccessSpan src;
                        plAccessGeometry::Instance()->OpenRO(dr, diIndex[k], src);

                        static hsTArray<plCutoutPoly> dst;
                        dst.SetCount(0);

                        plProfile_BeginTiming(Cutter);
                        fCutter->Cutout(src, dst);
                        plProfile_EndTiming(Cutter);

                        plProfile_BeginTiming(Process);
                        if( IProcessPolys(dr, diIndex[k], secs, dst) )
                        {
                            plProfile_BeginTiming(Callback);
                            if( src.HasWaterHeight() )
                                ICutoutCallback(dst, true, src.GetWaterHeight());
                            else
                                ICutoutCallback(dst);
                            plProfile_EndTiming(Callback);

                            retVal = true;
                        }
                        plProfile_EndTiming(Process);

                        plAccessGeometry::Instance()->Close(src);
                    }
                }
            }
        }
    }
    plProfile_EndTiming(Total);
    return retVal;
}

hsBool plDynaDecalMgr::ICutoutList(hsTArray<plDrawVisList>& drawVis, double secs)
{
    if( fDisableAccumulate )
        return false;

    hsBool retVal = false;

    if( !drawVis.GetCount() )
        return retVal;

    hsTArray<plAccessSpan> src;

    int numSpan = 0;
    int iDraw;
    for( iDraw = 0; iDraw < drawVis.GetCount(); iDraw++ )
        numSpan += drawVis[iDraw].fVisList.GetCount();

    src.SetCount(numSpan);

    int i;

    iDraw = 0;
    int iSpan = 0;
    for( i = 0; i < numSpan; i++ )
    {
        static hsTArray<plCutoutPoly> dst;
        dst.SetCount(0);

        plAccessGeometry::Instance()->OpenRO(drawVis[iDraw].fDrawable, drawVis[iDraw].fVisList[iSpan], src[i]);

        fCutter->Cutout(src[i], dst);

        if( IProcessPolys((plDrawableSpans*)drawVis[iDraw].fDrawable, drawVis[iDraw].fVisList[iSpan], secs, dst) )
            retVal = true;

        plAccessGeometry::Instance()->Close(src[i]);

        if( ++iSpan >= drawVis[iDraw].fVisList.GetCount() )
        {
            iDraw++;
            iSpan = 0;
        }
    }
    return retVal;
}

hsBool plDynaDecalMgr::ICutoutTargets(double secs)
{
    if( fDisableAccumulate )
        return false;

    hsBool retVal = false;

    int i;
    for( i = 0; i < fTargets.GetCount(); i++ )
    {
        if( fTargets[i] )
            retVal |= ICutoutObject(fTargets[i], secs);
    }
    return retVal;
}

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

hsGMaterial* plDynaDecalMgr::IConvertToEnvMap(hsGMaterial* mat, plBitmap* envMap)
{
    if( !mat || !envMap )
        return nil;

    plLayerInterface* oldLay = mat->GetLayer(0);

    plMipmap* oldMip = plMipmap::ConvertNoRef(oldLay->GetTexture());
    if( !oldMip )
        return mat;
    oldMip->SetCurrLevel(0);

    hsGMaterial* newMat = new hsGMaterial;
    plString buff = plString::Format("%s_EnvMat", GetKey()->GetName().c_str());
    hsgResMgr::ResMgr()->NewKey(buff, newMat, GetKey()->GetUoid().GetLocation());

    static plTweak<float> kSmooth(1.f);
    plMipmap* bumpMap = plBumpMapGen::QikNormalMap(nil, oldMip, 0xffffffff, plBumpMapGen::kBubbleTest, kSmooth);
//  plMipmap* bumpMap = plBumpMapGen::QikNormalMap(nil, oldMip, 0xffffffff, plBumpMapGen::kNormalize, kSmooth);
//  plMipmap* bumpMap = plBumpMapGen::QikNormalMap(nil, oldMip, 0xffffffff, 0, 0);
    buff = plString::Format("%s_BumpMap", GetKey()->GetName().c_str());
    hsgResMgr::ResMgr()->NewKey(buff, bumpMap, GetKey()->GetUoid().GetLocation());

    bumpMap->SetFlags(bumpMap->GetFlags() | plMipmap::kBumpEnvMap | plMipmap::kForceNonCompressed);

    plLayer* bumpLay = new plLayer;
    buff = plString::Format("%s_BumpMap_0", GetKey()->GetName().c_str());
    hsgResMgr::ResMgr()->NewKey(buff, bumpLay, GetKey()->GetUoid().GetLocation());

    bumpLay->SetState(oldLay->GetState());
    bumpLay->SetBlendFlags(hsGMatState::kBlendAdd | hsGMatState::kBlendEnvBumpNext);
    bumpLay->SetTransform(hsMatrix44::IdentityMatrix());
    bumpLay->SetUVWSrc(0);

    hsMatrix44 bumpEnvXfm;
    bumpEnvXfm.Reset();
    bumpLay->SetBumpEnvMatrix(bumpEnvXfm);

    bumpLay->SetAmbientColor(oldLay->GetAmbientColor());
    bumpLay->SetRuntimeColor(oldLay->GetRuntimeColor());
    bumpLay->SetOpacity(1.f);

    plLayRefMsg* refMsg = new plLayRefMsg(bumpLay->GetKey(), plRefMsg::kOnCreate, 0, plLayRefMsg::kTexture);
    hsgResMgr::ResMgr()->SendRef(bumpMap->GetKey(), refMsg, plRefFlags::kActiveRef);

    newMat->AddLayerViaNotify(bumpLay);

    plLayer* envLay = new plLayer;
    buff = plString::Format("%s_EnvMap_0", GetKey()->GetName().c_str());
    hsgResMgr::ResMgr()->NewKey(buff, envLay, GetKey()->GetUoid().GetLocation());

    envLay->SetBlendFlags(hsGMatState::kBlendMult);
    envLay->SetClampFlags(0);
    envLay->SetShadeFlags(bumpLay->GetShadeFlags());
    envLay->SetZFlags(hsGMatState::kZNoZWrite);
    envLay->SetMiscFlags(hsGMatState::kMiscUseReflectionXform);
    envLay->SetUVWSrc(plLayer::kUVWReflect);

    envLay->SetAmbientColor(oldLay->GetAmbientColor());
    envLay->SetRuntimeColor(oldLay->GetRuntimeColor());
    envLay->SetOpacity(1.f);

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

    newMat->AddLayerViaNotify(envLay);

    return newMat;
}

void plDynaDecalMgr::ConvertToEnvMap(plBitmap* envMap)
{
    hsGMaterial* newPreShade = IConvertToEnvMap(fMatPreShade, envMap);
    if( newPreShade && (newPreShade != fMatPreShade) )
        hsgResMgr::ResMgr()->SendRef(newPreShade->GetKey(), new plGenRefMsg(GetKey(), plRefMsg::kOnCreate, 0, kRefMatPreShade), plRefFlags::kActiveRef);
    
    hsGMaterial* newRTShade = IConvertToEnvMap(fMatRTShade, envMap);
    if( newRTShade && (newRTShade != fMatRTShade) )
        hsgResMgr::ResMgr()->SendRef(newRTShade->GetKey(), new plGenRefMsg(GetKey(), plRefMsg::kOnCreate, 0, kRefMatRTShade), plRefFlags::kActiveRef);
}

const plMipmap* plDynaDecalMgr::GetMipmap() const
{
    plMipmap* mip = nil;
    if( fMatRTShade )
        mip = plMipmap::ConvertNoRef(fMatRTShade->GetLayer(0)->GetTexture());

    if( !mip && fMatPreShade )
        mip = plMipmap::ConvertNoRef(fMatPreShade->GetLayer(0)->GetTexture());

    if( mip )
        mip->SetCurrLevel(0);

    return mip;
}

hsVector3 plDynaDecalMgr::IRandomUp(hsVector3 dir) const
{
    hsVector3 retVal;

    // Okay, we want a pretty random vector perpindicular to the
    // direction of fire. So we take that direction and cross product
    // it with the 3 world axes. Now we have 3 random vectors perpindicular
    // to the dir (but not necessarily each other). Note that some (but not all)
    // of these vectors may be zero length, it the direction happens to line up
    // with an axis. We scale each by a random amount, sum them up, and since
    // they are all perpindicular to dir, the weighted sum is too.
    // Only problem here is that our random scalings might wind us up with
    // a zero vector. Unlikely, which means almost certain to happen. So
    // we keep trying till we get a non-zero vector.
    float lenSq(-1.f);
    do {
        float ranXx = sRand.RandMinusOneToOne();
        float ranXy = sRand.RandMinusOneToOne();
        float ranXz = sRand.RandMinusOneToOne();
        retVal.fX = -dir.fZ * ranXy + dir.fY * ranXz;
        retVal.fY = dir.fZ * ranXx + -dir.fX * ranXz;
        retVal.fZ = -dir.fY * ranXx + dir.fX * ranXy;

        lenSq = retVal.MagnitudeSquared();

    } while( lenSq <= 0 );

    retVal *= hsFastMath::InvSqrtAppr(lenSq);

    return retVal;
}

hsVector3 plDynaDecalMgr::IReflectDir(hsVector3 dir) const
{
    hsFastMath::NormalizeAppr(dir); // it's been interpolated.

    // parm of zero returns unaffected dir, parm of one returns cutter direction reflected about dir.
    // Here's the original math.
    // Here N is dir, B is -cutter back direction (incoming), k is parm.
    // Reflection R of B is 2*(N dot B)*N + B
    // Interpolating gives K*R + (1-K)*N
    // Simplifying gives (2*K*(N dot B) + (1-K)) * N + K*B
    // Or something.

    plConst(float) parm(0.5f);

    hsVector3 b = -fCutter->GetBackDir();

    float t = dir.InnerProduct(b);
    t *= -2.f * parm;
    t += (1.f - parm);

    hsVector3 ret = dir * t;

    ret += b * parm;

    return ret;
}

hsMatrix44 plDynaDecalMgr::IL2WFromHit(hsPoint3 pos, hsVector3 dir) const
{
    dir = IReflectDir(dir);

    // Negate the firing direction before constructing our psys l2w, because
    // particles fire in the negative Z direction.
    dir = -dir;

    hsVector3 up = IRandomUp(dir);
    hsVector3 acc = up % dir;

    hsMatrix44 l2w;
    l2w.Reset();
    l2w.fMap[0][0] = acc[0];
    l2w.fMap[1][0] = acc[1];
    l2w.fMap[2][0] = acc[2];

    l2w.fMap[0][1] = up[0];
    l2w.fMap[1][1] = up[1];
    l2w.fMap[2][1] = up[2];

    l2w.fMap[0][2] = dir[0];
    l2w.fMap[1][2] = dir[1];
    l2w.fMap[2][2] = dir[2];

    l2w.fMap[0][3] = pos[0];
    l2w.fMap[1][3] = pos[1];
    l2w.fMap[2][3] = pos[2];

    l2w.NotIdentity();

    return l2w;
}

void plDynaDecalMgr::ICutoutCallback(const hsTArray<plCutoutPoly>& cutouts, hsBool hasWaterHeight, float waterHeight)
{
    hsTArray<plCutoutHit> hits;

    if( (fPartyTime > 0) && fParticles.GetCount() )
    {
        if( hasWaterHeight )
            fCutter->FindHitPointsConstHeight(cutouts, hits, waterHeight);
        else
            fCutter->FindHitPoints(cutouts, hits);

        int i;
        for( i = 0; i < hits.GetCount(); i++ )
        {
            int j;
            for( j = 0; j < fParticles.GetCount(); j++ )
            {
                plParticleEmitter* emit = fParticles[j]->GetAvailEmitter();
                if( emit )
                {
                    hsMatrix44 l2w = IL2WFromHit(hits[i].fPos, hits[i].fNorm);

                    emit->OverrideLocalToWorld(l2w);
                    emit->SetTimeToLive(fPartyTime);
                }
            }
        }
    }
}

void plDynaDecalMgr::IGetParticles()
{
    if( fParticles.GetCount() != fPartyObjects.GetCount() )
    {
        int i;
        for( i = 0; i < fPartyObjects.GetCount(); i++ )
        {
            const plParticleSystem *sys = plParticleSystem::ConvertNoRef(fPartyObjects[i]->GetModifierByType(plParticleSystem::Index()));
            // const_cast here is just to see if it's in our list, make Find happy.
            if( sys && (fParticles.kMissingIndex == fParticles.Find(const_cast<plParticleSystem*>(sys))) )
            {
                hsgResMgr::ResMgr()->AddViaNotify(sys->GetKey(), new plGenRefMsg(GetKey(), plRefMsg::kOnCreate, 0, kRefParticles), plRefFlags::kPassiveRef);
            }
        }
    }
}
