/*==LICENSE==*

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

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

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

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

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

*==LICENSE==*/

#include "hsTypes.h"
#include "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 "plMath/plRandom.h"
#include "hsFastMath.h"

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

#include "pnMessage/plPipeResMakeMsg.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 kDefMaxNumVerts = 1000;
static const UInt16 kDefMaxNumIdx = kDefMaxNumVerts;

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

static const hsScalar 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 = TRACKED_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, TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnCreate, 0, kRefMatPreShade), plRefFlags::kActiveRef);

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

    int n = stream->ReadSwap32();
    int i;
    for( i = 0; i < n; i++ )
    {
        mgr->ReadKeyNotifyMe(stream, TRACKED_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->ReadSwap32();
    for( i = 0; i < n; i++ )
    {
        mgr->ReadKeyNotifyMe(stream, TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnCreate, 0, kRefPartyObject), plRefFlags::kPassiveRef);
    }

    fMaxNumVerts = (UInt16)(stream->ReadSwap32());
    fMaxNumIdx = (UInt16)(stream->ReadSwap32());

    fWaitOnEnable = stream->ReadSwap32();

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

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

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

    fScale.Read(stream);

    fPartyTime = stream->ReadSwapScalar();

    n = stream->ReadSwap32();
    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->WriteSwap32(fTargets.GetCount());

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

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

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

    stream->WriteSwap32(fWaitOnEnable);

    stream->WriteSwapScalar(fIntensity);

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

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

    fScale.Write(stream);

    stream->WriteSwapScalar(fPartyTime);

    stream->WriteSwap32(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 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(UInt32(shape), shape->GetKey());
            IWetInfo(info, enaMsg);
        }
    }
    else
    if( enaMsg->GetID() == UInt32(-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(UInt32(shape), shape->GetKey());
                IWetInfo(info, enaMsg);
            }
        }
    }
    else
    {
        IWetPart(enaMsg->GetID(), enaMsg);
    }
    return true;
}

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

    const plPrintShape* shape = IGetPrintShape(avMod, id);
    if( shape )
    {
        plDynaDecalInfo& info = IGetDecalInfo(UInt32(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(UInt32(refMsg->GetRef()));
            return true;
        }
    }

    return plSynchedObject::MsgReceive(msg);
}

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

void plDynaDecalMgr::INotifyInactive(plDynaDecalInfo& info, const plKey& armKey, UInt32 id) const
{
    if( info.fFlags & plDynaDecalInfo::kActive )
    {
        double secs = hsTimer::GetSysSeconds();
        int i;
        for( i = 0; i < fNotifies.GetCount(); i++ )
        {
            plDynaDecalEnableMsg* enaMsg = TRACKED_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(UInt32 id, const plKey& key)
{
    plDynaDecalMap::iterator iter = fDecalMap.find(id);
    if( iter == fDecalMap.end() )
    {
        plDynaDecalInfo decalInfo;
        decalInfo.Init(key);
        plGenRefMsg* refMsg = TRACKED_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 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);
        }
    }
}

hsScalar 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.
    hsScalar wet = (hsScalar)(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 numVerts, UInt16 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 = TRACKED_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 = TRACKED_NEW plAuxSpan;
        fAuxSpans.Append(aux);
        IAllocAuxSpan(aux, fMaxNumVerts, fMaxNumIdx);
    }
}

void plDynaDecalMgr::IAllocAuxSpan(plAuxSpan* aux, UInt32 maxNumVerts, UInt32 maxNumIdx)
{
    int iGrp = fGroups.GetCount();
    plGBufferGroup* grp = TRACKED_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* dataPtr = nil;
    grp->ReserveIndexStorage(maxNumIdx, &aux->fIBufferIdx, &aux->fIStartIdx, &dataPtr);
    aux->fIStartIdx;

    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 numVerts, UInt16 numIdx)
{
    int idx = INewDecal();

    fDecals[idx]->fStartVtx = (UInt16)(aux->fVStartIdx + aux->fVLength);
    fDecals[idx]->fNumVerts = numVerts;
    fDecals[idx]->fStartIdx = (UInt16)(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& numVerts, UInt16& 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* ptr = grp->GetVertBufferData(auxSpan->fVBufferIdx);
    
    ptr += cell->fVtxStart + auxSpan->fCellOffset;

    return (plDecalVtxFormat*)ptr;

}

UInt16* 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 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* idx = IGetBaseIdxPtr(auxSpan);
    idx += decal->fStartIdx;

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

    UInt16 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 hsScalar totalDepth = fCutter->GetLengthW();

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

    plConst(hsScalar) kMinDepth(0.25f);
    plConst(hsScalar) 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;

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

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

        hsScalar 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* idx = IGetBaseIdxPtr(auxSpan);
    idx += decal->fStartIdx;

    UInt16 base = decal->fStartVtx;
    int j;
    for( j = 0; j < src.GetCount(); j++ )
    {
        UInt16 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;

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

        hsScalar 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* idx = IGetBaseIdxPtr(auxSpan);
    idx += decal->fStartIdx;

    UInt16 base = decal->fStartVtx;
    int j;
    for( j = 0; j < src.GetCount(); j++ )
    {
        UInt16 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 = (hsScalar)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* idx = IGetBaseIdxPtr(auxSpan);
    idx += decal->fStartIdx;

    UInt16 base = decal->fStartVtx;
    int j;
    for( j = 0; j < src.GetCount(); j++ )
    {
        UInt16 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 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;
}

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

#include "../plGImage/plBumpMapGen.h"

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 = TRACKED_NEW hsGMaterial;
    char buff[256];
    sprintf(buff, "%s_%s", GetKey()->GetName(), "EnvMat");
    hsgResMgr::ResMgr()->NewKey(buff, newMat, GetKey()->GetUoid().GetLocation());

    static plTweak<hsScalar> 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);
    sprintf(buff, "%s_%s", GetKey()->GetName(), "BumpMap");
    hsgResMgr::ResMgr()->NewKey(buff, bumpMap, GetKey()->GetUoid().GetLocation());

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

    plLayer* bumpLay = TRACKED_NEW plLayer;
    sprintf(buff, "%s_%s_%d", GetKey()->GetName(), "BumpMap", 0);
    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 = TRACKED_NEW plLayRefMsg(bumpLay->GetKey(), plRefMsg::kOnCreate, 0, plLayRefMsg::kTexture);
    hsgResMgr::ResMgr()->SendRef(bumpMap->GetKey(), refMsg, plRefFlags::kActiveRef);

    newMat->AddLayerViaNotify(bumpLay);

    plLayer* envLay = TRACKED_NEW plLayer;
    sprintf(buff, "%s_%s_%d", GetKey()->GetName(), "EnvMap", 0);
    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 = TRACKED_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(), TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnCreate, 0, kRefMatPreShade), plRefFlags::kActiveRef);
    
    hsGMaterial* newRTShade = IConvertToEnvMap(fMatRTShade, envMap);
    if( newRTShade && (newRTShade != fMatRTShade) )
        hsgResMgr::ResMgr()->SendRef(newRTShade->GetKey(), TRACKED_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.
    hsScalar lenSq(-1.f);
    do {
        hsScalar ranXx = sRand.RandMinusOneToOne();
        hsScalar ranXy = sRand.RandMinusOneToOne();
        hsScalar 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(hsScalar) parm(0.5f);

    hsVector3 b = -fCutter->GetBackDir();

    hsScalar 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, hsScalar 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(), TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnCreate, 0, kRefParticles), plRefFlags::kPassiveRef);
            }
        }
    }
}