/*==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==*/
//////////////////////////////////////////////////////////////////////////////
//                                                                          //
//  plDrawableSpans Class Export-only Functions                             //
//                                                                          //
//// Version History /////////////////////////////////////////////////////////
//                                                                          //
//  Created 4.3.2001 mcn                                                    //
//                                                                          //
//////////////////////////////////////////////////////////////////////////////

#include "HeadSpin.h"
#include "plDrawableSpans.h"
#include "hsStream.h"
#include "hsResMgr.h"
#include "plPipeline.h"
#include "plGeometrySpan.h"

#include "plSpaceTree.h"
#include "plSpaceTreeMaker.h"       // This is fun and amusing and wonderful to have here.
                                    // Keep it here forever.
#include "plSurface/hsGMaterial.h"
#include "plPipeline/plFogEnvironment.h"
#include "pnMessage/plRefMsg.h"
#include "pnMessage/plNodeRefMsg.h" // for NodeRefMsg
#include "plMessage/plDeviceRecreateMsg.h"
#include "plPipeline/plGBufferGroup.h"
#include "plSurface/hsGMaterial.h"
#include "plSurface/plLayerInterface.h"
#include "plGImage/plBitmap.h"
#include "plGLight/plLightInfo.h"
#include "plgDispatch.h"

#include "plStatusLog/plStatusLog.h"

//#define VERT_LOG

//// Write ///////////////////////////////////////////////////////////////////

void    plDrawableSpans::Write( hsStream* s, hsResMgr* mgr )
{
    uint32_t  i, j, count;
    
    // Make sure we're optimized before we write (should be tho)
//  Optimize();

    // Make sure all the garbage is cleaned up
    if( fNeedCleanup )
        IRemoveGarbage();

    // Parent write
    plDrawable::Write(s, mgr);

    s->WriteLE32( fProps );
    s->WriteLE32( fCriteria );
    s->WriteLE32( fRenderLevel.fLevel );

    /// Write out the material keys
    s->WriteLE32( fMaterials.GetCount() );
    for( i = 0; i < fMaterials.GetCount(); i++ )
        mgr->WriteKey( s, fMaterials[ i ] );

    /// Write out the icicles
    s->WriteLE32( fIcicles.GetCount() );
    for( i = 0; i < fIcicles.GetCount(); i++ )
        fIcicles[ i ].Write( s );

    /// Write out the patches
    // FIXME MAJOR VERSION
    // no more patches, remove this line
    s->WriteLE32(0);

    /// Write out the index table based on the pointer array
    count = fSpans.GetCount();
    s->WriteLE32( count );
    for( i = 0; i < count; i++ )
    {
        uint8_t   *icicle = (uint8_t *)fSpans[ i ], *base = (uint8_t *)fIcicles.AcquireArray();
        j = (uint32_t)( icicle - base ) / sizeof( plIcicle );
        s->WriteLE32( j );
    }

    /// Write out the common keys
    for( i = 0; i < count; i++ )
    {
        // The fog environ key
        mgr->WriteKey( s, fSpans[ i ]->fFogEnvironment );
    }

    /// Write out the bounds and stuff
    if( count > 0 )
    {
        fLocalBounds.Write(s);
        fWorldBounds.Write(s);
        fMaxWorldBounds.Write(s);
    }

    for( i = 0; i < count; i++ )
    {
        if( fSpans[i]->fProps & plSpan::kPropHasPermaLights )
        {
            uint32_t lcnt = fSpans[i]->fPermaLights.GetCount();
            s->WriteLE32(lcnt);
            int j;
            for( j = 0; j < lcnt; j++ )
                mgr->WriteKey( s, fSpans[i]->fPermaLights[j]);
        }
        if( fSpans[i]->fProps & plSpan::kPropHasPermaProjs )
        {
            uint32_t lcnt = fSpans[i]->fPermaProjs.GetCount();
            s->WriteLE32(lcnt);
            int j;
            for( j = 0; j < lcnt; j++ )
                mgr->WriteKey( s, fSpans[i]->fPermaProjs[j]);
        }
    }

    /// Write out the source spans if necessary
    s->WriteLE32( fSourceSpans.GetCount() );
    if( fSourceSpans.GetCount() > 0 )
    {
        for( i = 0; i < fSourceSpans.GetCount(); i++ )
            fSourceSpans[ i ]->Write( s );
    }

    count = fLocalToWorlds.GetCount();
    s->WriteLE32(count);
    for( i = 0; i < count; i++ )
    {
        fLocalToWorlds[i].Write(s);
        fWorldToLocals[i].Write(s);

        fLocalToBones[i].Write(s);
        fBoneToLocals[i].Write(s);
    }

    // Write out the drawInterface index arrays
    s->WriteLE32( fDIIndices.GetCount() );
    for( i = 0; i < fDIIndices.GetCount(); i++ )
    {
        plDISpanIndex   *array = fDIIndices[ i ];

        s->WriteLE32( array->fFlags );
        s->WriteLE32( array->GetCount() );
        for( j = 0; j < array->GetCount(); j++ )
            s->WriteLE32( (*array)[ j ] );
    }

    // Write the groups out
    count = fGroups.GetCount();

    s->WriteLE( count );
    for( i = 0; i < count; i++ )
    {
#ifdef VERT_LOG

        hsUNIXStream log;
        log.Open("log\\GBuf.log", "ab");
        char buf[256];
        sprintf(buf, "Drawable Span: %s, GroupNum: %u\r\n", GetKeyName(), i);
        log.WriteString(buf);
        log.Close();
#endif
        fGroups[ i ]->Write( s );
    }

    /// Other stuff
    mgr->WriteCreatable(s, fSpaceTree);

    mgr->WriteKey(s, fSceneNode);

    /// All done!
}

//// AddDISpans //////////////////////////////////////////////////////////////
//  Adds a drawInterface's geometry spans to the list to be collapsed into 
//  buffers.

uint32_t  plDrawableSpans::AddDISpans( hsTArray<plGeometrySpan *> &spans, uint32_t index )
{
    int             i;
    uint32_t          spanIdx;
    plSpan          *span;
    hsBounds3Ext    bounds;


    /// Do garbage cleanup first
    if( fNeedCleanup )
        IRemoveGarbage();

    if (index == (uint32_t)-1) // need a new one
    {
        /// Create a lookup entry
        index = fDIIndices.GetCount();
        fDIIndices.Append( new plDISpanIndex );
        fDIIndices[ index ]->fFlags = plDISpanIndex::kNone;
    }
    plDISpanIndex   *spanLookup = fDIIndices[ index ];


    /// Add the geometry spans to our list. Also add our internal span
    /// copies
    for( i = 0; i < spans.GetCount(); i++ )
    {
        spanLookup->Append( fSourceSpans.GetCount() );
        spans[ i ]->fSpanRefIndex = fSourceSpans.GetCount();
        fSourceSpans.Append( spans[ i ] );  

        spanIdx = fIcicles.GetCount();
        fIcicles.Append( plIcicle() );
        plIcicle *icicle = &fIcicles[ spanIdx ];
        span = (plSpan *)icicle;

        /// Set common stuff
        IAssignMatIdxToSpan( span, spans[ i ]->fMaterial );
        span->fLocalToWorld = spans[ i ]->fLocalToWorld;
        span->fWorldToLocal = spans[ i ]->fWorldToLocal;
        span->fProps |= ( spans[ i ]->fProps & plGeometrySpan::kPropRunTimeLight ) ? plSpan::kPropRunTimeLight : 0;
        if( spans[i]->fProps & plGeometrySpan::kPropNoShadowCast )
            span->fProps |= plSpan::kPropNoShadowCast;
        if( spans[i]->fProps & plGeometrySpan::kPropNoShadow )
            span->fProps |= plSpan::kPropNoShadow;
        if( spans[i]->fProps & plGeometrySpan::kPropForceShadow )
            span->fProps |= plSpan::kPropForceShadow;
        if( spans[i]->fProps & plGeometrySpan::kPropReverseSort )
            span->fProps |= plSpan::kPropReverseSort;
        if( spans[i]->fProps & plGeometrySpan::kPartialSort )
            span->fProps |= plSpan::kPartialSort;
        if( spans[i]->fProps & plGeometrySpan::kVisLOS )
        {
            span->fProps |= plSpan::kVisLOS;
            fProps |= plDrawable::kPropHasVisLOS;
        }

        span->fNumMatrices = spans[ i ]->fNumMatrices;
        span->fBaseMatrix = spans[ i ]->fBaseMatrix;
        span->fLocalUVWChans = spans[i]->fLocalUVWChans;
        span->fMaxBoneIdx = spans[i]->fMaxBoneIdx;
        span->fPenBoneIdx = (uint16_t)(spans[i]->fPenBoneIdx);

        bounds = spans[ i ]->fLocalBounds;
        span->fLocalBounds = bounds;
        bounds.Transform( &span->fLocalToWorld );
        span->fWorldBounds = bounds;
        span->fFogEnvironment = spans[ i ]->fFogEnviron;

        /// Add to our source indices
        fSpans.Append( span );
        fSpanSourceIndices.Append( spanIdx );
    }

    /// Rebuild the pointer array
    IRebuildSpanArray();

    SetSpaceTree(nil);

    fOptimized = false;

    return index;
}

//// Optimize ////////////////////////////////////////////////////////////////

void    plDrawableSpans::Optimize( void )
{
    int     i;

    if( fOptimized )
        return;

    /// Sort all the source spans
    if( !(fProps & kPropNoReSort) )
        ISortSourceSpans();

    /// Pack the source spans into spans and indices
    IPackSourceSpans();

    /// Now that we're done with the source spans, get rid of them! (BLEAH!)
    for( i = 0; i < fSourceSpans.GetCount(); i++ )
        delete fSourceSpans[ i ];
    fSourceSpans.Reset();

    if( fCriteria & kCritSortSpans )
    {
        if( !(fProps & kPropNoReSort) )
            fProps |= kPropSortSpans;
    }
    if( fCriteria & kCritSortFaces )
    {
        fProps |= kPropSortFaces;
    }

    /// Now we do a pass at the buffer groups, asking them to tidy up
    for( i = 0; i < fGroups.GetCount(); i++ )
        fGroups[ i ]->TidyUp();

    // Look to see if we have any materials we aren't using anymore.
    for( i = 0; i < fMaterials.GetCount(); i++ )
    {
        ICheckToRemoveMaterial(i);
    }

    fReadyToRender = false;

    // Make the space tree (hierarchical bounds).
    plSpaceTreeMaker maker;
    maker.Reset();
    for( i = 0; i < GetNumSpans(); i++ )
    {
        maker.AddLeaf( fSpans[ i ]->fWorldBounds, fSpans[ i ]->fProps & plSpan::kPropNoDraw  );
    }
    plSpaceTree* tree = maker.MakeTree();
    SetSpaceTree(tree);

    /// All done!
    fOptimized = true;
}

static plStatusLog* IStartLog(const char* name, int numSpans)
{
    static char buff[256];
    sprintf(buff, "x%s.log", name);

    plStatusLog* statusLog = plStatusLogMgr::GetInstance().CreateStatusLog(
        plStatusLogMgr::kDefaultNumLines, 
        buff, 
        plStatusLog::kFilledBackground | plStatusLog::kDeleteForMe );
    return statusLog;
}

static plStatusLog* IEndLog(plStatusLog* statusLog)
{
    delete statusLog;
    return nil;
}

static void ILogSpan(plStatusLog* statusLog, plGeometrySpan* geo, plVertexSpan* span, plGBufferGroup* group)
{
    if( span->fTypeMask & plSpan::kIcicleSpan )
    {
        plIcicle* ice = (plIcicle*)span;
        if( geo->fProps & plGeometrySpan::kFirstInstance )
        {
            plGBufferCell* cell = group->GetCell(span->fVBufferIdx, span->fCellIdx);
            uint32_t stride = group->GetVertexSize();
            uint32_t ptr = cell->fVtxStart + span->fCellOffset * stride;

            statusLog->AddLineF("From obj <%s> mat <%s> size %d bytes grp=%d (%d offset)",
                geo->fMaxOwner.s_str("<unknown>"),
                geo->fMaterial ? geo->fMaterial->GetKey()->GetName().c_str() : "<unknown>",
                geo->GetVertexSize(geo->fFormat) * geo->fNumVerts + sizeof(uint16_t) * geo->fNumIndices,
                span->fGroupIdx,
                ptr
                );
//              span->fVBufferIdx,
//              span->fCellIdx,
//              span->fCellOffset,
//              span->fVStartIdx,
//              span->fVLength,
//              ice->fIBufferIdx,
//              ice->fIStartIdx,
//              ice->fILength
        }
        else
        {
            statusLog->AddLineF("Instanced obj <%s> mat <%s> grp=%d (%d/%d/%d/%d/%d/%d/%d/%d)",
                geo->fMaxOwner.s_str("<unknown>"),
                geo->fMaterial ? geo->fMaterial->GetKey()->GetName().c_str() : "<unknown>",
                span->fGroupIdx,
                span->fVBufferIdx,
                span->fCellIdx,
                span->fCellOffset,
                span->fVStartIdx,
                span->fVLength,
                ice->fIBufferIdx,
                ice->fIStartIdx,
                ice->fILength
                );
        }
    }
    else
    {
        if( geo->fProps & plGeometrySpan::kFirstInstance )
        {
            statusLog->AddLineF("From obj <%s> mat <%s> size %d bytes grp=%d (%d/%d/%d/%d/%d)",
                geo->fMaxOwner.s_str("<unknown>"),
                geo->fMaterial ? geo->fMaterial->GetKey()->GetName().c_str() : "<unknown>",
                geo->GetVertexSize(geo->fFormat) * geo->fNumVerts + sizeof(uint16_t) * geo->fNumIndices,
                span->fGroupIdx,
                span->fVBufferIdx,
                span->fCellIdx,
                span->fCellOffset,
                span->fVStartIdx,
                span->fVLength
                );
        }
        else
        {
            statusLog->AddLineF("Instanced obj <%s> mat <%s> grp=%d (%d/%d/%d/%d/%d)",
                geo->fMaxOwner.s_str("<unknown>"),
                geo->fMaterial ? geo->fMaterial->GetKey()->GetName().c_str() : "<unknown>",
                span->fGroupIdx,
                span->fVBufferIdx,
                span->fCellIdx,
                span->fCellOffset,
                span->fVStartIdx,
                span->fVLength
                );
        }
    }
}

//// IPackSourceSpans ////////////////////////////////////////////////////////
//  Takes the array of source spans and converts them to our internal icicle
//  spans, vertex buffers and index buffers.

void    plDrawableSpans::IPackSourceSpans( void )
{
    int             i, j;
    hsBounds3Ext    bounds;
    hsBitVector     doneSpans;


    /// Calc bounds
    fLocalBounds.MakeEmpty();
    fWorldBounds.MakeEmpty();

    for( i = 0; i < fSourceSpans.GetCount(); i++ )
    {
        hsBounds3Ext    bnd = fSourceSpans[ i ]->fLocalBounds;
        bnd.Transform( &fSourceSpans[ i ]->fLocalToWorld );
        fWorldBounds.Union( &bnd );
    }

    fLocalBounds = fWorldBounds;
    fLocalBounds.Transform( &fWorldToLocal );
    fMaxWorldBounds = fWorldBounds;

    // It could be that instance refs in spans that we (the drawable) own
    // are actually spans in some other drawable. That's not currently handled.
    // (Making that case handled would involve rewriting the instancing implementation
    // to not suck ass).
    // So for each instance set, we make two lists of instance refs, the ones also
    // in this drawable (refsHere), and ones not (refsThere). If there are refsThere,
    // we split out the refsHere into a new instance group, removing them from the
    // refsThere list. If refsThere still contains spans from separate drawables, 
    // that will be dealt with in those drawables' Optimize calls.
    doneSpans.Clear();
    for( i = 0; i < fSourceSpans.GetCount(); i++ )
    {
        if( !doneSpans.IsBitSet(i) )
        {
            plGeometrySpan* span = fSourceSpans[i];
            if( span->fInstanceRefs )
            {
                hsTArray<plGeometrySpan*>& refs = *(span->fInstanceRefs);
                hsTArray<plGeometrySpan*> refsHere;
                hsTArray<plGeometrySpan*> refsThere;

                int k;
                for( k = 0; k < refs.GetCount(); k++ )
                {
                    plGeometrySpan* other = refs[k];
                    if( other != span )
                    {
                        int idx = fSourceSpans.Find(other);
                        if( fSourceSpans.kMissingIndex == idx )
                        {
                            refsThere.Append(other);
                        }
                        else
                        {
                            refsHere.Append(other);
                        }
                    }
                }
                if( refsThere.GetCount() )
                {
                    if( refsHere.GetCount() )
                    {
                        span->BreakInstance();

                        // Okay, got to form a new instance group out of refsHere.
                        for( k = 0; k < refsHere.GetCount(); k++ )
                        {
                            plGeometrySpan* other = refsHere[k];

                            other->ChangeInstance(span);

                            doneSpans.SetBit(other->fSpanRefIndex);
                        }
                    }
                    else
                    {
                        span->UnInstance();
                    }
                    if( refsThere.GetCount() == 1 )
                    {
                        refsThere[0]->UnInstance();
                    }
                }
            }
            doneSpans.SetBit(i);
        }
    }

    /// Now pack the spans
    doneSpans.Clear();
    for( i = 0; i < fSourceSpans.GetCount(); i++ )
    {
        // Now we fill the rest of the data in for our span
        if( !doneSpans.IsBitSet( i ) )
        {
            if( fSourceSpans[ i ]->fProps & plGeometrySpan::kInstanced )
            {
                // Instanced spans--convert the first as normal, then convert the rest
                // using the first as reference
                doneSpans.SetBit( i, true );

                plIcicle    *baseIcicle = (plIcicle *)fSpans[ i ];
                IConvertGeoSpanToIcicle( fSourceSpans[ i ], baseIcicle, 0, baseIcicle );

                // Loop through the rest
                for( j = 0; j < fSourceSpans[ i ]->fInstanceRefs->GetCount(); j++ )
                {
                    plGeometrySpan *other = (*fSourceSpans[ i ]->fInstanceRefs)[ j ];
                    if( other == fSourceSpans[ i ] )
                        continue;

#if 0 // What exactly is this supposed to be doing? My guess is, NADA.
                    if( IConvertGeoSpanToIcicle( other, (plIcicle *)fSpans[ other->fSpanRefIndex ], 0, baseIcicle ) )
                        baseIcicle = (plIcicle *)fSpans[ other->fSpanRefIndex ];
#else // What exactly is this supposed to be doing? My guess is, NADA.
                    IConvertGeoSpanToIcicle( other, (plIcicle *)fSpans[ other->fSpanRefIndex ], 0, baseIcicle );
#endif // What exactly is this supposed to be doing? My guess is, NADA.
                    doneSpans.SetBit( other->fSpanRefIndex, true );
                }
    
            }
            else
            {
                // Do normal, uninstanced conversion
                IConvertGeoSpanToIcicle( fSourceSpans[ i ], (plIcicle *)fSpans[ i ], 0 );   
                doneSpans.SetBit( i, true );
            }
        }
    }

#ifdef VERT_LOG
    hsTArray<plGeometrySpan*> order;
    order.SetCount(fSourceSpans.GetCount());
    for( i = 0; i < fSourceSpans.GetCount(); i++ )
    {
        order[fSourceSpans[i]->fSpanRefIndex] = fSourceSpans[i];
    }

    plStatusLog* statusLog = IStartLog(GetKey()->GetName(), fSourceSpans.GetCount());
    for( i = 0; i < order.GetCount(); i++ )
    {
        plVertexSpan* vSpan = (plVertexSpan*)fSpans[i];
        ILogSpan(statusLog, order[i], vSpan, fGroups[vSpan->fGroupIdx]);
    }
    statusLog = IEndLog(statusLog);
#endif
}

//// ISortSourceSpans ////////////////////////////////////////////////////////
//  Does our actual optimization path by resorting all the spans into the
//  most efficient order possible. Also has to re-order the span lookup
//  table.

void    plDrawableSpans::ISortSourceSpans( void )
{
    hsTArray<uint32_t>    spanReorderTable, spanInverseTable;
    int                 i, j, idx;
    plGeometrySpan      *tmpSpan;
    uint32_t              tmpIdx;
    plSpan              *tmpSpanPtr;


    // Init the reorder table
    for( i = 0; i < fSourceSpans.GetCount(); i++ )
        spanReorderTable.Append( i );

    // Do a nice, if naiive, sort by material (hehe by the pointers, no less)
    for( i = 0; i < fSourceSpans.GetCount() - 1; i++ )
    {
        for( j = i + 1, idx = i; j < fSourceSpans.GetCount(); j++ )
        {
            if( ICompareSpans( fSourceSpans[ j ], fSourceSpans[ idx ] ) < 0 )
                idx = j;
        }

        // Swap idx with i, so we get the smallest pointer on top
        if( i != idx )
        {
            // Swap both the source span and our internal span
            tmpSpan = fSourceSpans[ i ];
            fSourceSpans[ i ] = fSourceSpans[ idx ];
            fSourceSpans[ idx ] = tmpSpan;

            fSourceSpans[ i ]->fSpanRefIndex = i;
            fSourceSpans[ idx ]->fSpanRefIndex = idx;

            tmpSpanPtr = fSpans[ i ];
            fSpans[ i ] = fSpans[ idx ];
            fSpans[ idx ] = tmpSpanPtr;

            // Also swap the entries in the reorder table
            tmpIdx = spanReorderTable[ i ];
            spanReorderTable[ i ] = spanReorderTable[ idx ];
            spanReorderTable[ idx ] = tmpIdx;
        }

        // Next!
    }


    /// Problem: our reorder table is inversed(y->x instead of x->y). Either we search for numbers,
    /// or we just flip it first...
    spanInverseTable.SetCountAndZero( spanReorderTable.GetCount() );
    for( i = 0; i < spanReorderTable.GetCount(); i++ )
        spanInverseTable[ spanReorderTable[ i ] ] = i;

    /// Now update our span xlate table
    for( i = 0; i < fDIIndices.GetCount(); i++ )
    {
        if( !fDIIndices[ i ]->IsMatrixOnly() )
        {
            for( j = 0; j < fDIIndices[ i ]->GetCount(); j++ )
            {
                idx = (*fDIIndices[ i ])[ j ];

                (*fDIIndices[ i ])[ j ] = spanInverseTable[ idx ];
            }
        }
    }

    /// Use our pointer array to rebuild the icicle array (UUUUGLY)
    hsTArray<plIcicle>      tempIcicles;
    plIcicle                *newIcicle;

    tempIcicles.SetCount( fIcicles.GetCount() );

    for( i = 0, newIcicle = tempIcicles.AcquireArray();
         i < fSpans.GetCount(); i++ )
    {
        *newIcicle = *( (plIcicle *)fSpans[ i ] );
        fSpans[ i ] = newIcicle;
        newIcicle++;    
    }

    /// Swap the two arrays out. This will basically swap the actual memory blocks, so be careful...
    fIcicles.Swap( tempIcicles );
    tempIcicles.Reset();
}

//// ICompareSpans ///////////////////////////////////////////////////////////
//  Sorting function for ISortSpans(). Kinda like strcmp(): returns -1 if 
//  span1 < span2, 1 if span1 > span2, 0 if "equal".

short   plDrawableSpans::ICompareSpans( plGeometrySpan *span1, plGeometrySpan *span2 )
{
    bool        b1, b2;
    int         i, j, numLayers;
    plBitmap    *t1, *t2;


    /// Quick check--identical materials are easy to compare :)
    if( span1->fMaterial == span2->fMaterial )
        return 0;

    /// Compare features from most to least important...

    // Any decal span should come after a non-decal
    if( span1->fDecalLevel < span2->fDecalLevel )
        return -1;
    if( span1->fDecalLevel > span2->fDecalLevel )
        return 1;
    // Ok, they're equal decal-wise, so find something else to judge on

    // Most important: is one of the materials an alpha blend? (if so, gotta
    // put at end, so it's "bigger")
    if( span1->fMaterial->GetNumLayers() > 0 && 
        ( span1->fMaterial->GetLayer( 0 )->GetState().fBlendFlags & hsGMatState::kBlendMask ) != 0 )
        b1 = true;
    else
        b1 = false;

    if( span2->fMaterial->GetNumLayers() > 0 && 
        ( span2->fMaterial->GetLayer( 0 )->GetState().fBlendFlags & hsGMatState::kBlendMask ) != 0 )
        b2 = true;
    else
        b2 = false;

    if( b1 != b2 )
        return( b1 ? 1 : -1 );

    // Next is texture (name). We do this kinda like strings: compare the first layer's
    // textures and go upwards, so that we group materials together starting with the
    // base layer's texture and going upwards
    numLayers = span1->fMaterial->GetNumLayers();
    if( span2->fMaterial->GetNumLayers() < numLayers )
        numLayers = span2->fMaterial->GetNumLayers();

    for( i = 0; i < numLayers; i++ )
    {
        t1 = span1->fMaterial->GetLayer( i )->GetTexture();
        t2 = span2->fMaterial->GetLayer( i )->GetTexture();

        if( t1 != nil && t2 == nil )
            return 1;
        else if( t1 == nil && t2 != nil )
            return -1;
        else if( t1 == nil && t2 == nil )
            break;  // Textures equal up to here--keep going with rest of tests
        
        if( !t1->GetKeyName().IsNull() && !t2->GetKeyName().IsNull() )
        {
            j = t1->GetKeyName().Compare( t2->GetKeyName(), plString::kCaseInsensitive );
            if( j != 0 )
                return (short)j;
        }
    }

    // Finally, by material itself.
    if( !span1->fMaterial->GetKeyName().IsNull() && !span2->fMaterial->GetKeyName().IsNull() )
    {
        j = span1->fMaterial->GetKeyName().Compare( span2->fMaterial->GetKeyName(), plString::kCaseInsensitive );
        if( j != 0 )
            return (short)j;
    }

    if( span1->fLocalToWorld.fFlags != span2->fLocalToWorld.fFlags )
    {
        if( span1->fLocalToWorld.fFlags )
            return -1;
        else
            return 1;
    }

    /// Equal in our book...
    return 0;
}