/*==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 "hsMaterialConverter.h"
#include <commdlg.h>
#include <math.h>
#include <float.h>

#include "Max.h"
#include "stdmat.h"
#include "bmmlib.h"
#include "istdplug.h"
#include "texutil.h"

#include "plLayerConverter.h"
#include "MaxComponent/plMaxAnimUtils.h"
#include "plResMgr/plKeyFinder.h"
#include "hsResMgr.h"
#include "pnKeyedObject/plUoid.h"

#include "hsMaxLayerBase.h"
#include "MaxExport/plErrorMsg.h"
#include "plSurface/hsGMaterial.h"
#include "pnSceneObject/plSceneObject.h"
#include "UserPropMgr.h"
#include "plFile/plFileUtils.h"

#include "hsConverterUtils.h"
#include "hsControlConverter.h"
#include "MaxMain/plMaxNode.h"
#include "MaxMain/MaxCompat.h"

#include "plInterp/plController.h"
#include "hsExceptionStack.h"
#include "hsStringTokenizer.h"
#include "plSurface/plLayerInterface.h"
#include "plSurface/plLayer.h"
#include "plSurface/plLayerAnimation.h"
#include "plGImage/plMipmap.h"

#include "plgDispatch.h"

#include "pnMessage/plRefMsg.h"
#include "pnKeyedObject/plKey.h"
#include "pnKeyedObject/plKeyImp.h"

#include "plBitmapCreator.h"

#include "plMessage/plMatRefMsg.h"
#include "plMessage/plLayRefMsg.h"
#include "pnMessage/plObjRefMsg.h"
#include "pnMessage/plNodeRefMsg.h"
#include "pfMessage/plClothingMsg.h"

#include "MaxPlasmaMtls/Materials/plMultipassMtlPB.h"
#include "MaxPlasmaMtls/Materials/plCompositeMtlPB.h"
#include "MaxPlasmaMtls/Materials/plPassMtl.h"
#include "MaxPlasmaMtls/Materials/plMultipassMtl.h"
#include "MaxPlasmaMtls/Materials/plDecalMtl.h"
#include "MaxPlasmaMtls/Materials/plCompositeMtl.h"
#include "MaxPlasmaMtls/Materials/plParticleMtl.h"
#include "MaxPlasmaMtls/Materials/plBumpMtl.h"
#include "MaxPlasmaMtls/Materials/plPassMtlBase.h"
#include "MaxPlasmaMtls/Materials/plAnimStealthNode.h"
#include "MaxPlasmaMtls/Layers/plPlasmaMAXLayer.h"
#include "MaxPlasmaMtls/Layers/plLayerTex.h"
#include "MaxPlasmaMtls/Layers/plLayerTexBitmapPB.h"

#include "pfSurface/plLayerAVI.h"
#include "pfSurface/plLayerBink.h"

#include "MaxComponent/plLightMapComponent.h"
#include "plDrawable/plGeometrySpan.h"

#include "MaxPlasmaMtls/Materials/plClothingMtl.h"
#include "plAvatar/plAvatarClothing.h"
#include "plAvatar/plClothingLayout.h"
#include "plSDL/plSDL.h"
#include "plSDL/plSDLDescriptor.h"

extern UserPropMgr gUserPropMgr;

static const char* kSecretBumpSign = "~~~";

namespace {
    const int kDefaultDetailBias=5;

    void CopyMaterialLODToTextures(hsGMaterial* mat)
    {
        int32_t i;
        for (i = 0; i < mat->GetNumLayers(); ++i)
        {
            plLayerInterface* layer = mat->GetLayer(i);

/*          Textures? What textures?
            if (layer->GetTexture())
            {
                hsGTexture* texture = layer->GetTexture();
                texture->SetLOD(texture->GetLOD() | mat->GetLOD());
            }
*/
        }
    }

    const char  sWarnBaseTextureMissing[] = "The object \"%s\"'s material has a base layer that is assigned texture \"%s\", but the texture file is missing. "
                                        "This can cause unwanted effects during runtime."; 
    const char  sWarnUpperTextureMissing[] = "The object \"%s\"'s material has an upper layer that is assigned texture \"%s\", but the texture file is missing. "
                                        "This is not supported in the engine, so the upper layer will be ignored."; 
    const char  sWarnNoUpperTexture[] = "The object \"%s\"'s material has an uppper layer that is not assigned a texture. "
                                        "This is not supported in the engine, so the upper layer will be disabled."; 
}

static uint32_t MakeUInt32Color(float r, float g, float b, float a)
{
    return (uint32_t(a * 255.9f) << 24)
            |(uint32_t(r * 255.9f) << 16)
            |(uint32_t(g * 255.9f) << 8)
            |(uint32_t(b * 255.9f) << 0);
}

static bool failedRT = false;
static bool failedNumUV = false;
static bool failedAlphaLayer = false;
static bool failedFade = false;
static int dupCuzRT = 0;
static int dupCuzNumUV = 0;
static int dupCuzAlphaLayer = 0;
static int dupCuzFade = 0;

hsMaterialConverter& hsMaterialConverter::Instance()
{
    hsGuardBegin("hsMaterialConverter::Instance");
    static hsMaterialConverter the_instance;

    return the_instance;
    hsGuardEnd; 
}

hsMaterialConverter::hsMaterialConverter() :
fSave(true),
fNodeName(nil),
fWarned(0),
fInterface(nil),
fConverterUtils(hsConverterUtils::Instance()),
fChangedTimes(false)
{
    hsGuardBegin("hsMaterialConverter::hsMaterialConverter");

    hsGuardEnd;
}

hsMaterialConverter::~hsMaterialConverter()
{
    hsGuardBegin("hsMaterialConverter::~hsMaterialConverter");
    hsAssert(fDoneMaterials.Count() == 0, "FreeMaterialCache not called");
    hsGuardEnd;
}

void hsMaterialConverter::Init(hsBool save, plErrorMsg *msg)
{
    hsGuardBegin("hsMaterialConverter::Init");

    fInterface = GetCOREInterface();
    fSave = save;
    fErrorMsg = msg;

    fSubIndex = -1;
    fNodeName = nil;

    fWarned = true;

    fLastMaterial.fHsMaterial = nil;
    fLastMaterial.fMaxMaterial = nil;
    fLastMaterial.fMaxMaterial = nil;
    fLastMaterial.fSubMultiMat = false;
    fLastMaterial.fOwnedCopy = false;

    failedRT = false;
    failedNumUV = false;
    failedAlphaLayer = false;
    failedFade = false;
    dupCuzRT = 0;
    dupCuzNumUV = 0;
    dupCuzAlphaLayer = 0;
    dupCuzFade = 0;

    hsGuardEnd;
}

void hsMaterialConverter::FreeMaterialCache(const char* path)
{
    if( path && *path )
        IGenMaterialReport(path);

    for (int i = 0; i < fDoneMaterials.Count(); i++)
        fDoneMaterials[i].fHsMaterial->GetKey()->UnRefObject();

    fDoneMaterials.Reset();
}

hsBool hsMaterialConverter::ForceNoUvsFlatten(plMaxNode* node)
{
    hsGuardBegin("hsMaterialConverter::ForceNoUvsFlatten");
    return IsMultiMat(GetBaseMtl(node));
    hsGuardEnd; 
}

hsBool hsMaterialConverter::PreserveUVOffset(Mtl* mtl)
{
    hsGuardBegin("hsMaterialConverter::PreserveUVOffset");

    if (!mtl || (!IsHsMaxMat(mtl) && !IsDecalMat(mtl)))
        return true;

    //IParamBlock2 *pblock = mtl->GetParamBlockByID(plPassMtl::kBlkLayers);
    //if (!pblock)
    //  return true;

    plPassMtlBase *currMtl = (plPassMtlBase *)mtl;
    for (int i = 0; i < mtl->NumSubTexmaps(); i++) 
    {
        //if (i == 1 && !pblock->GetInt(kPassLayTopOn))
        if (i == 1 && !currMtl->GetTopLayerOn())
            continue;

        Texmap *texMap = mtl->GetSubTexmap(i);

        if (!texMap || texMap->ClassID() != LAYER_TEX_CLASS_ID)
            continue;
        
        StdUVGen* uvGen = (StdUVGen*)((plLayerTex*)texMap)->GetTheUVGen();
        int tiling = uvGen->GetTextureTiling();
        if( !(tiling & U_WRAP) || !(tiling & V_WRAP) )
            return true;

        if (IHasAnimatedControllers(uvGen))
            return true;
//      if (ITextureTransformIsAnimated(texMap))
//          return true;
        
    }

    return false;

    hsGuardEnd; 
}

void AttachLinkMtlAnims(plMaxNode *node, hsGMaterial *mat)
{
    const int numKeys = 2;
    float times[] = {0.f, 1.5f};
    float values[numKeys] = {100.f, 0.f};
    hsBool leaving[] = {true, false};
    char *animName = "_link_anim";

    int k;
    for (k = 0; k < mat->GetNumLayers(); k++)
    {
        plLayerInterface *oldLayer, *currLayer;
        oldLayer = currLayer = mat->GetLayer(k);
        plLeafController *opaCtl;
        plLayerLinkAnimation* animLayer;

        char suff[10];
        sprintf(suff, "%d", k);
        
        opaCtl = new plLeafController;
        opaCtl->QuickScalarController(numKeys, times, values, sizeof(float));
        animLayer = new plLayerLinkAnimation;
        animLayer->SetLinkKey(node->GetAvatarSO()->GetKey());
        //animLayer->fLeavingAge = leaving[x];
        plString fullAnimName = plString::Format("%s_%s_%s", oldLayer->GetKeyName().c_str(), animName, suff);
        hsgResMgr::ResMgr()->NewKey(fullAnimName, animLayer, node->GetLocation());
        animLayer->SetOpacityCtl(opaCtl);
        animLayer->GetTimeConvert().SetBegin(times[0]);
        animLayer->GetTimeConvert().SetEnd(times[1]);
        animLayer->GetTimeConvert().Stop(true);
        animLayer->AttachViaNotify(currLayer);
        currLayer = animLayer;
        
        plMatRefMsg* msg = new plMatRefMsg(mat->GetKey(), plRefMsg::kOnReplace, k, plMatRefMsg::kLayer);
        msg->SetOldRef(oldLayer);
        msg->SetRef(currLayer);
        hsgResMgr::ResMgr()->AddViaNotify(msg, plRefFlags::kActiveRef);
    }
}

uint32_t hsMaterialConverter::ColorChannelsUseMask(plMaxNode* node, int iSubMtl)
{
    uint32_t usedChan = 0;
    hsBool deleteIt = false;

    TriObject* triObj = node->GetTriObject(deleteIt);
    if( triObj )
    {
        Mesh* mesh = &(triObj->mesh);
    
        UVVert* alphaMap = mesh->mapVerts(MAP_ALPHA);   
        int numAlphaVerts = mesh->getNumMapVerts(MAP_ALPHA);

        UVVert* illumMap = mesh->mapVerts(MAP_SHADING);
        int numIllumVerts = mesh->getNumMapVerts(MAP_SHADING);

        Point3* colorMap = mesh->vertCol;
        int numColorVerts = mesh->numCVerts;

        TVFace* colorFaces = mesh->vcFace;
        TVFace* illumFaces = mesh->mapFaces(MAP_SHADING);
        TVFace* alphaFaces = mesh->mapFaces(MAP_ALPHA);

        int numFaces = mesh->getNumFaces();
        int i;
        for( i = 0; i < numFaces; i++ )
        {
            Face        *face = &mesh->faces[ i ];

            if( (iSubMtl >= 0) && (face->getMatID() != iSubMtl) )
                continue;

            if( colorFaces && colorMap )
            {
                TVFace* colorFace = colorFaces + i;
                usedChan |= ICheckPoints(colorMap[colorFace->getTVert(0)],
                                        colorMap[colorFace->getTVert(1)],
                                        colorMap[colorFace->getTVert(2)],
                                        0,
                                        kColorRedBlack, kColorRedGrey, kColorRedWhite);
                usedChan |= ICheckPoints(colorMap[colorFace->getTVert(0)],
                                        colorMap[colorFace->getTVert(1)],
                                        colorMap[colorFace->getTVert(2)],
                                        1,
                                        kColorGreenBlack, kColorGreenGrey, kColorGreenWhite);
                usedChan |= ICheckPoints(colorMap[colorFace->getTVert(0)],
                                        colorMap[colorFace->getTVert(1)],
                                        colorMap[colorFace->getTVert(2)],
                                        2,
                                        kColorBlueBlack, kColorBlueGrey, kColorBlueWhite);
            }
            if( illumFaces && illumMap )
            {
                TVFace* illumFace = illumFaces + i;
                usedChan |= ICheckPoints(illumMap[illumFace->getTVert(0)],
                                        illumMap[illumFace->getTVert(1)],
                                        illumMap[illumFace->getTVert(2)],
                                        0,
                                        kIllumRedBlack, kIllumRedGrey, kIllumRedWhite);
                usedChan |= ICheckPoints(illumMap[illumFace->getTVert(0)],
                                        illumMap[illumFace->getTVert(1)],
                                        illumMap[illumFace->getTVert(2)],
                                        1,
                                        kIllumGreenBlack, kIllumGreenGrey, kIllumGreenWhite);
                usedChan |= ICheckPoints(illumMap[illumFace->getTVert(0)],
                                        illumMap[illumFace->getTVert(1)],
                                        illumMap[illumFace->getTVert(2)],
                                        2,
                                        kIllumBlueBlack, kIllumBlueGrey, kIllumBlueWhite);
            }
            if( alphaFaces && alphaMap )
            {
                TVFace* alphaFace = alphaFaces + i;
                usedChan |= ICheckPoints(alphaMap[alphaFace->getTVert(0)],
                                        alphaMap[alphaFace->getTVert(1)],
                                        alphaMap[alphaFace->getTVert(2)],
                                        0,
                                        kAlphaBlack, kAlphaGrey, kAlphaWhite);
            }
        }

        if( deleteIt )
            triObj->DeleteThis();
    }

    return usedChan;
}

uint32_t hsMaterialConverter::ICheckPoints(const Point3& p0, const Point3& p1, const Point3& p2,
                                         int chan,
                                         uint32_t mBlack, uint32_t mGrey, uint32_t mWhite)
{
    const float kSmall = 1.e-3f;
    if( (p0[chan] < kSmall) && (p1[chan] < kSmall) && (p2[chan] < kSmall) )
        return mBlack;

    if( (1.f - p0[chan] < kSmall) && (1.f - p1[chan] < kSmall) && (1.f - p2[chan] < kSmall) )
        return mWhite;

    return mGrey;
}

uint32_t hsMaterialConverter::ICheckPoints(const Point3& p0, const Point3& p1, const Point3& p2, const Point3& p3,
                                         int chan,
                                         uint32_t mBlack, uint32_t mGrey, uint32_t mWhite)
{
    const float kSmall = 1.e-3f;
    if( (p0[chan] < kSmall) && (p1[chan] < kSmall) && (p2[chan] < kSmall) && (p3[chan] < kSmall) )
        return mBlack;

    if( (1.f - p0[chan] < kSmall) && (1.f - p1[chan] < kSmall) && (1.f - p2[chan] < kSmall) && (1.f - p3[chan] < kSmall) )
        return mWhite;

    return mGrey;
}

int hsMaterialConverter::NumVertexOpacityChannelsRequired(plMaxNode* node, int iSubMtl)
{

    uint32_t vtxChanMask = VertexChannelsRequiredMask(node, iSubMtl);

    // Now count the bits.
    int numChan;
    for( numChan = 0; vtxChanMask; vtxChanMask >>= 1 )
    {
        numChan += (vtxChanMask & 0x1);
    }
    return numChan;
}

uint32_t hsMaterialConverter::VertexChannelsRequiredMask(plMaxNode* node, int iSubMtl)
{
    Mtl* mtl = node->GetMtl();
    if( !mtl )
        return 0;

    // We use iSubMtl < 0 here as a request for all submtls used by node.
    if( IsMultiMat(mtl) && (iSubMtl >= 0) )
    {
        if( iSubMtl < 0 )
            iSubMtl = 0;
        while( IsMultiMat(mtl) )
        {
            if( iSubMtl >= mtl->NumSubMtls() )
                iSubMtl = mtl->NumSubMtls() - 1;
            mtl = mtl->GetSubMtl(iSubMtl);
        }
    }
    else
    {
        iSubMtl = -1;
    }

    // Get the channels our materials will look at. These should all
    // be of the grey variety, since a solid opaque can be drawn as is,
    // and a solid transparent can be pitched.
    uint32_t vtxChanReq = VertexChannelsRequestMask(node, iSubMtl, mtl);

    // Or in vtx alpha. If it's been set, we need to respect it, if it hasn't,
    // it'll and out to zero anyway when we & with the vtxChanUsed.
    vtxChanReq |= kAlphaGrey;

    // Now figure out what's in all the channels, which are solid black, which
    // actually have interesting values etc.
    uint32_t vtxChanUsed = ColorChannelsUseMask(node, iSubMtl);

    uint32_t vtxChanMask = vtxChanReq & vtxChanUsed;

    return vtxChanMask;
}

uint32_t hsMaterialConverter::VertexChannelsRequestMask(plMaxNode* node, int iSubMtl, Mtl* mtl)
{
    if( !mtl )
        return 0;

    uint32_t vtxChanMask = 0;

    if( IsMultiMat(mtl) && (iSubMtl >= 0) )
    {
        while( IsMultiMat(mtl) )
        {
            if( iSubMtl >= mtl->NumSubMtls() )
                iSubMtl = mtl->NumSubMtls() - 1;
            mtl = mtl->GetSubMtl(iSubMtl);
        }
    }

    if( IsMultiMat(mtl) || IsMultipassMat(mtl) )
    {
        int i;

        for( i = 0; i < mtl->NumSubMtls(); i++ )
            vtxChanMask |= VertexChannelsRequestMask(node, iSubMtl, mtl->GetSubMtl(i));

        return vtxChanMask;
    }

    if( IsCompositeMat(mtl) )
    {
        vtxChanMask |= VertexChannelsRequestMask(node, iSubMtl, mtl->GetSubMtl(0));

        int i;
        for( i = 1; i < mtl->NumSubMtls(); i++ )
        {
            plCompositeMtl* comp = (plCompositeMtl*)mtl;
            int myBlend = comp->GetBlendStyle(i);
            switch( myBlend )
            {
            case plCompositeMtl::kCompBlendVertexAlpha:
            case plCompositeMtl::kCompBlendInverseVtxAlpha:
                vtxChanMask |= kAlphaGrey;
                break;
            case plCompositeMtl::kCompBlendVertexIllumRed:
            case plCompositeMtl::kCompBlendInverseVtxIllumRed:
                vtxChanMask |= kIllumRedGrey;
                break;
            case plCompositeMtl::kCompBlendVertexIllumGreen:
            case plCompositeMtl::kCompBlendInverseVtxIllumGreen:
                vtxChanMask |= kIllumGreenGrey;
                break;
            case plCompositeMtl::kCompBlendVertexIllumBlue:
            case plCompositeMtl::kCompBlendInverseVtxIllumBlue:
                vtxChanMask |= kIllumBlueGrey;
                break;
            default:
                hsAssert(false, "Ooops, new composite blends?");
                break;
            }
        }
    }

    if( IsHsMaxMat(mtl) )
    {
        plPassMtl* passMtl = (plPassMtl*)mtl;
        if( plPassMtlBase::kBlendAlpha == passMtl->GetOutputBlend() )
            vtxChanMask |= kAlphaGrey;
    }

    return vtxChanMask;
}


//
// What kind of material am I??
//
hsBool hsMaterialConverter::IsMultiMat(Mtl *m) 
{
    hsGuardBegin("hsMaterialConverter::IsMultiMat");

    if (m == NULL)
    {
        return false;
    }

    return (m->ClassID() == Class_ID(MULTI_CLASS_ID,0));
    hsGuardEnd; 
}

hsBool hsMaterialConverter::IsHsMaxMat(Mtl *m)
{
    hsGuardBegin("hsMaterialConverter::IsHsMaxMat");

    if (m==NULL)
    {
        return false;
    }

    return (m->ClassID() == PASS_MTL_CLASS_ID);
    hsGuardEnd; 
}

hsBool hsMaterialConverter::IsMultipassMat(Mtl *m)
{
    hsGuardBegin("hsMaterialConverter::IsHsMaxMat");

    if (m==NULL)
    {
        return false;
    }

    return (m->ClassID() == MULTIMTL_CLASS_ID);
    hsGuardEnd; 
}

hsBool hsMaterialConverter::IsDecalMat(Mtl *m)
{
    hsGuardBegin("hsMaterialConverter::IsDecalMat");

    if (m==NULL)
    {
        return false;
    }

    return (m->ClassID() == DECAL_MTL_CLASS_ID);
    hsGuardEnd; 
}

hsBool hsMaterialConverter::IsCompositeMat(Mtl *m)
{
    hsGuardBegin("hsMaterialConverter::IsCompositeMat");

    if (m==NULL)
    {
        return false;
    }

    return (m->ClassID() == COMP_MTL_CLASS_ID);
    hsGuardEnd; 
}

hsBool hsMaterialConverter::IsParticleMat(Mtl *m)
{
    hsGuardBegin("hsMaterialConverter::IsParticleMat");

    if (m==NULL)
    {
        return false;
    }

    return (m->ClassID() == PARTICLE_MTL_CLASS_ID);
    hsGuardEnd;
}

hsBool hsMaterialConverter::IsClothingMat(Mtl *m)
{
    hsGuardBegin("hsMaterialConverter::IsClothingMat");

    if (m==NULL)
    {
        return false;
    }

    return (m->ClassID() == CLOTHING_MTL_CLASS_ID);
    hsGuardEnd;
}

hsBool hsMaterialConverter::IsTwoSided(Mtl* m, int iSubMtl)
{
    if( !m )
        return false;

    return 0 != (m->Requirements(iSubMtl) & MTLREQ_2SIDE);
}

hsBool hsMaterialConverter::HasAnimatedTextures(Texmap* texMap)
{
    hsGuardBegin("hsMaterialConverter::HasAnimatedTextures");
    return false;
    hsGuardEnd; 
}

hsBool hsMaterialConverter::IsAVILayer(Texmap*  texMap)
{
    hsGuardBegin("hsMaterialConverter::IsAVILayer");
    return false;
    hsGuardEnd; 
}

hsBool hsMaterialConverter::IsQTLayer(Texmap*  texMap)
{
    hsGuardBegin("hsMaterialConverter::IsQTLayer");
    return false;
    hsGuardEnd; 
}

hsBool hsMaterialConverter::IsBinkLayer(Texmap*  texMap)
{
    hsGuardBegin("hsMaterialConverter::IsBinkLayer");
    return false;
    hsGuardEnd; 
}



// MAXR3 broke GetCoordMapping, here's a work around which
// works for everything except "Texture - Planar from Object XYZ"
int hsMaterialConverter::GetCoordMapping(StdUVGen *uvgen)
{
    hsGuardBegin("hsMaterialConverter::GetCoordMapping");

    // Try Requirements, as StdUV does override this...
    ULONG reqs = 0l;
    reqs = uvgen->Requirements(0);
    if (reqs & MTLREQ_VIEW_DEP || reqs == 0) 
    {
        return (uvgen->GetCoordMapping(0));
    } 
    else 
    {
        return (UVMAP_EXPLICIT);
    }

    hsGuardEnd; 
}

static void IGetNodesByMaterialRecur(plMaxNode* node, Mtl *mtl, hsTArray<plMaxNode*> &out)
{
    if (node)
    {
        if (node->GetMtl() == mtl)
            out.Append(node);

        int numChildren = node->NumberOfChildren();
        for (int i = 0; i < numChildren; i++)
        {
            IGetNodesByMaterialRecur((plMaxNode*)node->GetChildNode(i), mtl, out);
        }
    }
}

void hsMaterialConverter::GetNodesByMaterial(Mtl *mtl, hsTArray<plMaxNode*> &out)
{
    IGetNodesByMaterialRecur((plMaxNode*)GetCOREInterface()->GetRootNode(), mtl, out);
}

//     Ok, Max gives us Multi-materials which it knows are just collections of independent materials. This function
// should never be called with a multi-mat. Instead, the multi-mat should call this for each one of its 
// sub-materials, and build up an array of these arrays.
//     This function should be called on anything that Max thinks is a single material, even if the exporter
// wants to generate several materials from it (like composites).

hsTArray<plExportMaterialData> *
hsMaterialConverter::CreateMaterialArray(Mtl *maxMaterial, plMaxNode *node, uint32_t multiIndex)
{
    hsTArray<plExportMaterialData> *ourMaterials = new hsTArray<plExportMaterialData>;

    const char* dbgNodeName = node->GetName();

    int numUVChannels = node->NumUVWChannels();
    hsBool makeAlphaLayer = node->AlphaHackLayersNeeded(multiIndex) > 0;

    hsBool enviro = fConverterUtils.IsEnvironHolder(node);
    
    plString name;
    if (maxMaterial)
        name = plString::FromUtf8(maxMaterial->GetName());
    else
        name = _TEMP_CONVERT_FROM_LITERAL("nil");
    
    /// Get the material
    hsBool isMultiMat = IsMultiMat( maxMaterial );
    if (isMultiMat)
    {
        if (fErrorMsg->Set(!(fWarned & kWarnedSubMulti), node->GetName(), "Multi-material in CreateMaterialArray (Multi child of multi?) on mat %s. Using the first sub-material instead.", 
            maxMaterial->GetName()).CheckAskOrCancel() )
            fWarned |= kWarnedSubMulti;
        maxMaterial = maxMaterial->GetSubMtl(0);
    }
        
    hsBool isMultipassMat = IsMultipassMat( maxMaterial );
    hsBool isComposite = IsCompositeMat( maxMaterial );
    
    int i;
    int numMaterials = 1;
    int numBlendChannels = 0;
    if (isComposite)
    {
        int numBlendMaterials = maxMaterial->NumSubMtls() - 1; // no blend for the base Mtl

        // This will be one or zero, depending on blend modes, and whether we can use the lighting equation
        // with vertex alpha.
        int maxBlendChannel = plGeometrySpan::kMaxNumUVChannels - 1 - numUVChannels;

        IParamBlock2 *pb = maxMaterial->GetParamBlockByID(plCompositeMtl::kBlkPasses);
        for (i = 0; i < numBlendMaterials; i++)
        {
            int curr = 0;
            if (curr > maxBlendChannel)
            {
                curr = maxBlendChannel;
            }
            pb->SetValue(kCompUVChannels, 0, (int)(numUVChannels + curr), i); // label each layer's opacity UV mapping
        }
        numMaterials = (1 << maxMaterial->NumSubMtls()) - 1; // 2^n - 1 possibilites

        ourMaterials->Reset();
        for( i = 0; i < numMaterials; i++ ) // would be smarter to only create the materials we'll actually use
        {
            plExportMaterialData emd;
            emd.fMaterial = ICreateMaterial(maxMaterial, node, name, i + 1, numUVChannels, makeAlphaLayer);
            emd.fNumBlendChannels = (emd.fMaterial != nil && emd.fMaterial->NeedsBlendChannel() ? 1 : 0);

            // A bump layer requires 2 generated uv channels
            if( HasBumpLayer(node, maxMaterial) )
                emd.fNumBlendChannels += 2;

            if (numBlendChannels < emd.fNumBlendChannels)
                numBlendChannels = emd.fNumBlendChannels;
            ourMaterials->Append(emd);
        }
    }
    else // plPassMtl, plDecalMat, plMultiPassMtl, plParticleMtl
    {
        hsGMaterial *mat = ICreateMaterial(maxMaterial, node, name, -1, numUVChannels, makeAlphaLayer);
        int maxLayer = (mat == nil ? 0 : mat->GetNumLayers());

        numBlendChannels = (mat != nil && mat->NeedsBlendChannel() ? 1 : 0);

        // A bump layer requires 2 generated uv channels
        if( HasBumpLayer(node, maxMaterial) )
            numBlendChannels += 2;

        plExportMaterialData emd;
        emd.fMaterial = mat;
        emd.fNumBlendChannels = numBlendChannels;
        ourMaterials->Reset();
        ourMaterials->Append(emd);
    }

    // We've already handled it... just letting them know.
    if (fErrorMsg->Set( (numUVChannels + numBlendChannels > plGeometrySpan::kMaxNumUVChannels) && !(fWarned & kWarnedTooManyUVs), node->GetName(), 
                        "Material wants %d UV channels for textures and blending, but only %d are available."
                        " Some layers will have incorrect channels assigned.",
                        numUVChannels + numBlendChannels, plGeometrySpan::kMaxNumUVChannels).CheckAskOrCancel() )
        fWarned |= kWarnedTooManyUVs; 

    return ourMaterials;
}

int hsMaterialConverter::MaxUsedUVWSrc(plMaxNode* node, Mtl* mtl)
{
    const char* dbgNodeName = node->GetName();

    if( !mtl )
        return 0;

    if( IsMultiMat(mtl) || IsMultipassMat(mtl) || IsCompositeMat(mtl) )
    {
        int i;

        int numUVWs = 0;
        for( i = 0; i < mtl->NumSubMtls(); i++ )
        {
            int num = MaxUsedUVWSrc(node, mtl->GetSubMtl(i));
            if( num > numUVWs )
                numUVWs = num;
        }
        return numUVWs;
    }

    if( IsParticleMat(mtl) )
    {
        return 1;
    }

    if( IsHsMaxMat(mtl) || IsDecalMat(mtl) || IsBumpMtl(mtl) )
    {
        plPassMtlBase* passMtl = (plPassMtlBase*)mtl;

        int numUVWs = 0;

        Texmap* tex = passMtl->GetBaseLayer();
        if( tex )
        {
            if( tex->GetUVWSource() == UVWSRC_EXPLICIT )
            {
                if( tex->GetMapChannel() > numUVWs )
                    numUVWs = tex->GetMapChannel();

                if( passMtl->GetTopLayerOn() && passMtl->GetTopLayer() )
                {
                    tex = passMtl->GetTopLayer();
                    if( tex->GetMapChannel() > numUVWs )
                        numUVWs = tex->GetMapChannel();
                }
            }

        }
        return numUVWs;
    }

    return 0;
}

hsGMaterial* hsMaterialConverter::NonAlphaHackPrint(plMaxNode* node, Texmap* baseTex, uint32_t blendFlags)
{
    // Bogus input, I hope they choke on the nil pointer I'm returning.
    if( !(baseTex && node) )
        return nil;

    plString name = plString::Format("%s_%s_%d", node->GetName(), baseTex->GetName(), 0);

    // Search done materials for it

    hsGMaterial* mat = new hsGMaterial;
    hsgResMgr::ResMgr()->NewKey(name, mat, node->GetLocation());

    // If plasmaLayer is nil, the artist has some other wierd (unsupported) layer type in the slot.
    // Should warn them here.
    plPlasmaMAXLayer* plasmaLayer = plPlasmaMAXLayer::GetPlasmaMAXLayer(baseTex);
    if( !plasmaLayer )
        return nil;

    plLayerInterface *layerIFace = plLayerConverter::Instance().ConvertTexmap(baseTex, node, 0, false, false); 

    plLayer* baseLay = plLayer::ConvertNoRef(layerIFace->BottomOfStack());
    if( !baseLay )
        return nil;

    baseLay->SetTransform(hsMatrix44::IdentityMatrix());
    baseLay->SetUVWSrc(0);
    baseLay->SetBlendFlags(blendFlags);
    baseLay->SetZFlags(hsGMatState::kZNoZWrite | hsGMatState::kZIncLayer);
    baseLay->SetShadeFlags(0);
    baseLay->SetClampFlags(hsGMatState::kClampTexture);
    baseLay->SetMiscFlags(0);

    baseLay->SetAmbientColor(hsColorRGBA().Set(0.f, 0.f, 0.f, 1.f));
    baseLay->SetRuntimeColor(hsColorRGBA().Set(1.f, 1.f, 1.f, 1.f));
    baseLay->SetPreshadeColor(hsColorRGBA().Set(0.f, 0.f, 0.f, 1.f));

    IAddLayerToMaterial(mat, layerIFace);

    return mat;
}

hsGMaterial* hsMaterialConverter::AlphaHackPrint(plMaxNode* node, Texmap* baseTex, uint32_t blendFlags)
{
    // Bogus input, I hope they choke on the nil pointer I'm returning.
    if( !(baseTex && node) )
        return nil;

    plString name = plString::Format("%s_%s_%d_AH", node->GetName(), baseTex->GetName(), 0);

    // Search done materials for it

    hsGMaterial* mat = new hsGMaterial;
    hsgResMgr::ResMgr()->NewKey(name, mat, node->GetLocation());

    // If plasmaLayer is nil, the artist has some other wierd (unsupported) layer type in the slot.
    // Should warn them here.
    plPlasmaMAXLayer* plasmaLayer = plPlasmaMAXLayer::GetPlasmaMAXLayer(baseTex);
    if( !plasmaLayer )
        return nil;

    plLayerInterface *layerIFace = plLayerConverter::Instance().ConvertTexmap(baseTex, node, 0, false, false); 

    plLayer* baseLay = plLayer::ConvertNoRef(layerIFace->BottomOfStack());
    if( !baseLay )
        return nil;

    baseLay->SetTransform(hsMatrix44::IdentityMatrix());
    baseLay->SetUVWSrc(0);
    baseLay->SetBlendFlags(blendFlags);
    baseLay->SetZFlags(hsGMatState::kZNoZWrite | hsGMatState::kZIncLayer);
    baseLay->SetShadeFlags(0);
    baseLay->SetClampFlags(hsGMatState::kClampTexture);
    baseLay->SetMiscFlags(0);

    baseLay->SetAmbientColor(hsColorRGBA().Set(0.f, 0.f, 0.f, 1.f));
    baseLay->SetRuntimeColor(hsColorRGBA().Set(1.f, 1.f, 1.f, 1.f));
    baseLay->SetPreshadeColor(hsColorRGBA().Set(1.f, 1.f, 1.f, 1.f));

    IAddLayerToMaterial(mat, layerIFace);

    plMipmap *texture = IGetUVTransTexture(node);
    IInsertSingleBlendLayer(texture, mat, node, 1, 1);

    return mat;
}

hsGMaterial* hsMaterialConverter::NonAlphaHackVersion(plMaxNode* node, Mtl* mtl, int subIndex)
{
    if( !mtl )
        return nil;

    if( IsMultiMat(mtl) )
    {
        return nil;
    }

    plString name = plString::Format("%s_%s_%d", node->GetName(), mtl->GetName(), subIndex);

    return ICreateMaterial(mtl, node, name, subIndex, 1, false);
}

hsGMaterial* hsMaterialConverter::AlphaHackVersion(plMaxNode* node, Mtl* mtl, int subIndex)
{
    if( !mtl )
        return nil;

    if( IsMultiMat(mtl) )
    {
        return nil;
    }

    plString name = plString::Format("%s_%s_%d_AH", node->GetName(), mtl->GetName(), subIndex);

    return ICreateMaterial(mtl, node, name, subIndex, 1, true);
}

//
// Big kahuna converter function
// (Though meshConverter should be calling CreateMaterialArray instead)
//
hsGMaterial *hsMaterialConverter::ICreateMaterial(Mtl *mtl, plMaxNode *node, const plString &name, int subIndex,
                                                  int numUVChannels, hsBool makeAlphaLayer)
{
    hsGuardBegin("hsMaterialConverter::ICreateMaterial");

    char *nodeName = node->GetName();
    fSubIndex = subIndex;
    fNodeName = nodeName;
    Object *obj = node->EvalWorldState(fConverterUtils.GetTime(fInterface)).obj;
    if (obj && (obj->SuperClassID() == SClass_ID(LIGHT_CLASS_ID)))
    {
        return ICheckForProjectedTexture(node);
    }
    else
    {
        hsBool forceCopy = hsControlConverter::Instance().OwnsMaterialCopy(node);
        if( !forceCopy )
            forceCopy = node->GetForceMaterialCopy();
        if( !forceCopy )
            forceCopy = IMustBeUniqueMaterial( mtl );

        hsBool runtimeLit = node->GetRunTimeLight();

        Mtl *bMtl = GetBaseMtl(node);
        hsBool isMultiMat = IsMultiMat(bMtl);// || IsPortalMat(bMtl);
        
        hsGMaterial *mat;
        if (mtl)
        {
            if (IsMatchingDoneMaterial(&fLastMaterial, mtl, isMultiMat, subIndex, forceCopy, runtimeLit,
                                       node, numUVChannels, makeAlphaLayer))
            {
                mat = fLastMaterial.fHsMaterial;
                CopyMaterialLODToTextures(mat);
                //hsRefCnt_SafeRef(mat);
                return mat;
            }

            int32_t i;
            int32_t index(-1);
            for (i = 0; i < fDoneMaterials.Count(); i++)
            {
                if (IsMatchingDoneMaterial(&fDoneMaterials[i], mtl,  isMultiMat, subIndex, forceCopy, runtimeLit,
                                           node, numUVChannels, makeAlphaLayer))
                {
                    index = i;
                    break;
                }               
            }

            if (index != -1)
            {
                mat = fDoneMaterials[index].fHsMaterial;
            
                fLastMaterial = fDoneMaterials[index];
    
                CopyMaterialLODToTextures(mat);
                //hsRefCnt_SafeRef(mat);
                return mat;
            }
        }
        // Guess we haven't converted this one before.  Bummer.
        
        // Get material
        /* // CreateMaterial is not called on MultiMats (if CreateMaterialArray is doing its job)
        if (IsMultiMat(mtl))
        {
            mat = IProcessMaterial(mtl->GetSubMtl(subIndex), node, numUVChannels);
        }
        */
        if (IsCompositeMat(mtl))
        {
            mat = IProcessMaterial(mtl, node, name, numUVChannels, subIndex);
            IInsertCompBlendingLayers(mtl, node, mat, subIndex, numUVChannels, makeAlphaLayer);
        }
        else if (IsMultipassMat(mtl))
        {           
            mat = IProcessMaterial(mtl, node, name, numUVChannels);
            IInsertMultipassBlendingLayers(mtl, node, mat, numUVChannels, makeAlphaLayer);
        }
        else if (IsHsMaxMat(mtl) || IsDecalMat(mtl))
        {           
            mat = IProcessMaterial(mtl, node, name, numUVChannels);
            IInsertAlphaBlendingLayers(mtl, node, mat, numUVChannels, makeAlphaLayer);
        }
        else if (IsClothingMat(mtl))
        {
            mat = nil; // clothing materials do not generate an hsGMaterial object
        }
        else { // Particle materials hit this. No need for blending layers, ever.
            mat = IProcessMaterial(mtl, node, name, numUVChannels);
        }
        if( mat && HasBumpLayer(node, mtl) )
            IInsertBumpLayers(node, mat);

        if (mat)
            mat = IInsertDoneMaterial(mtl, mat, node, isMultiMat, forceCopy, runtimeLit, subIndex, numUVChannels, makeAlphaLayer);

        return mat;
    }
    return nil;
    hsGuardEnd; 
}

#include "MaxPlasmaMtls/Materials/plMultipassMtl.h"

//
// Handle materials for normal non-light, non-particle nodes.
//
hsGMaterial *hsMaterialConverter::IProcessMaterial(Mtl *mtl, plMaxNode *node, const plString &name,
                                                   int UVChan, int subMtlFlags /* = 0 */)
{
    hsGuardBegin("hsMaterialConverter::IProcessMaterial");

    plLocation nodeLoc = node->GetLocation();

    char *dbgNodeName = node->GetName();
    hsGMaterial *hMat = nil;
    fChangedTimes = false;

    if (IsMultiMat(mtl))
    {
        if (fErrorMsg->Set(!(fWarned & kWarnedSubMulti), dbgNodeName, "Multi-material in ProcessMaterial (Multi child of multi?) on mat %s.", 
            mtl->GetName()).CheckAskOrCancel() )
            fWarned |= kWarnedSubMulti;
        hMat = IProcessMaterial(mtl->GetSubMtl(0), node, name, UVChan);
    }
    else if (IsHsMaxMat(mtl) || IsDecalMat(mtl) || IsBumpMtl( mtl ) ) 
    {
        hMat = new hsGMaterial;
        hsgResMgr::ResMgr()->NewKey(name, hMat,nodeLoc);
        IProcessPlasmaMaterial(mtl, node, hMat, hMat->GetKey()->GetName());
    }
    else if (mtl && mtl->ClassID() == MULTIMTL_CLASS_ID)
    {
        hMat = IProcessMultipassMtl(mtl, node, name, UVChan);
    }
    else if (IsCompositeMat(mtl))
    {
        hMat = IProcessCompositeMtl(mtl, node, name, UVChan, subMtlFlags);
    }
    else if (IsParticleMat(mtl))
    {
        hMat = IProcessParticleMtl(mtl, node, name);
    }
    else
    {
        hMat = IAddDefaultMaterial(node);
    }

    if (hMat)
    {
        if (node->GetAvatarSO() != nil)
        {
            AttachLinkMtlAnims(node, hMat);
        }
        if (hMat->GetNumLayers() == 0)
        {
            if (fErrorMsg->Set((fWarned & kWarnedNoLayers) == 0, node->GetName(), "Material has no layers. (%s)", mtl->GetName()).CheckAndAsk())
                fWarned |= kWarnedNoLayers;
            
            plLayer* hLay = new plLayer;
            hLay->InitToDefault();
            hsgResMgr::ResMgr()->NewKey(name + _TEMP_CONVERT_FROM_LITERAL("_DefLay"), hLay, nodeLoc);
            IAddLayerToMaterial(hMat, hLay);
        }

        if( node->UserPropExists("WetMe") && (hMat->GetKey()->GetName().Find("Wet(*)") < 0) )
            IAppendWetLayer(node, hMat);
//      hsgResMgr::ResMgr()->NewKey(name, hMat,nodeLoc);
    }
    else
        return nil;

    if (hMat->IsDynamic())
    {
        fChangedTimes = true;
    }

    CopyMaterialLODToTextures(hMat);

    return hMat;
    hsGuardEnd; 
}

hsBool hsMaterialConverter::IsMatchingDoneMaterial(DoneMaterialData *dmd, 
                                                   Mtl *mtl, hsBool isMultiMat, uint32_t subMtlFlags, hsBool forceCopy, hsBool runtimeLit,
                                                   plMaxNode *node, int numUVChannels, hsBool makeAlphaLayer)
{
    if (!((dmd->fMaxMaterial == mtl) && 
          (dmd->fSubMultiMat == isMultiMat) &&
          (dmd->fSubMtlFlags == subMtlFlags) &&
          (dmd->fRuntimeLit == runtimeLit) && 
          (dmd->fNumUVChannels == numUVChannels) &&
          (dmd->fMakeAlphaLayer == makeAlphaLayer)))
    {
        if( dmd->fMaxMaterial == mtl )
        {
            if( dmd->fRuntimeLit != runtimeLit )
                failedRT = true;
            if( dmd->fNumUVChannels != numUVChannels )
                failedNumUV = true;
            if( dmd->fMakeAlphaLayer != makeAlphaLayer )
                failedAlphaLayer = true;
        }

        return false;
    }
    if( dmd->fNode->HasFade() != node->HasFade() )
    {
        bool realFade = false;
        if( dmd->fNode->HasFade() )
        {
            Box3 fade = dmd->fNode->GetFade();
            if( (fade.Min()[0] != fade.Min()[1]) || (fade.Max()[0] != fade.Max()[1]) )
                realFade = true;
        }
        else if( node->HasFade() )
        {
            Box3 fade = node->GetFade();
            if( (fade.Min()[0] != fade.Min()[1]) || (fade.Max()[0] != fade.Max()[1]) )
                realFade = true;
        }

        if( realFade )
        {
            failedFade = true;
            return false;
        }
    }
    if( dmd->fNode->HasFade() && node->HasFade() )
    {
        Box3 dmdFade = dmd->fNode->GetFade();
        Box3 nodeFade = node->GetFade();
        if( dmdFade.Min() != nodeFade.Min() )
        {
            if( (dmdFade.Min()[0] != dmdFade.Min()[1]) || (nodeFade.Min()[0] != nodeFade.Min()[1]) )
            {
                failedFade = true;
                return false;
            }
        }
        if( dmdFade.Max() != nodeFade.Max() )
        {
            if( (dmdFade.Max()[0] != dmdFade.Max()[1]) || (nodeFade.Max()[0] != nodeFade.Max()[1]) )
            {
                failedFade = true;
                return false;
            }
        }
    }
                
    return !(forceCopy || dmd->fOwnedCopy) || (dmd->fNode == node);
}
        

hsGMaterial* hsMaterialConverter::IInsertDoneMaterial(Mtl *mtl, hsGMaterial *hMat, plMaxNode *node, hsBool isMultiMat, 
                                              hsBool forceCopy, hsBool runtimeLit, uint32_t subMtlFlags, int numUVChannels, 
                                              hsBool makeAlphaLayer)
{
    if( failedRT )
        dupCuzRT++;
    if( failedNumUV )
        dupCuzNumUV++;
    if( failedAlphaLayer )
        dupCuzAlphaLayer++;
    if( failedFade )
        dupCuzFade++;
    failedRT = failedNumUV = failedAlphaLayer = failedFade = false;

    DoneMaterialData done;
    done.fHsMaterial = hMat;
//  hsRefCnt_SafeAssign(done.fHsMaterial, hMat);
    done.fHsMaterial = hMat;
    done.fMaxMaterial = mtl;
    done.fNode = node;
    done.fSubMultiMat = isMultiMat;
    done.fOwnedCopy = forceCopy;
    done.fRuntimeLit = runtimeLit;
    done.fSubMtlFlags = subMtlFlags;
    done.fNumUVChannels = numUVChannels;
    done.fMakeAlphaLayer = makeAlphaLayer;

    DoneMaterialData* equivalent = IFindDoneMaterial(done);
    
    if( equivalent )
    {
        plKey matKey = hMat->GetKey();
        matKey->RefObject();
        matKey->UnRefObject();
        ((plKeyImp *)matKey)->SetObjectPtr(nil);
        matKey = nil;

        hMat = equivalent->fHsMaterial;
    }
    else
    {
        hMat->GetKey()->RefObject(); // Matching unref in hsMaterialConverter::DeInit();

        fDoneMaterials.Append(done);
    }

    return hMat;
}

hsGMaterial *hsMaterialConverter::IAddDefaultMaterial(plMaxNode *node)
{
    if (!node)
        return nil;

    plLocation loc = node->GetLocation();

    hsGMaterial *hMat = new hsGMaterial;
    hsgResMgr::ResMgr()->NewKey(plString::Format("%s_DefMat", node->GetName()), hMat, loc);
    
    plLayer *layer = new plLayer;
    layer->InitToDefault();
    hsgResMgr::ResMgr()->NewKey(hMat->GetKeyName() + _TEMP_CONVERT_FROM_LITERAL("_DefLay"), layer, loc);

    DWORD color = node->GetWireColor();
    float r = float(GetRValue(color)) / 255.f;
    float g = float(GetGValue(color)) / 255.f;
    float b = float(GetBValue(color)) / 255.f;
    layer->SetRuntimeColor(hsColorRGBA().Set(r, g, b, 1.f));
    layer->SetPreshadeColor(hsColorRGBA().Set(r, g, b, 1.f));
    layer->SetOpacity(1.f);
    IAddLayerToMaterial(hMat, layer);

    return hMat;
}

plMipmap *hsMaterialConverter::IGetUVTransTexture(plMaxNode *node, hsBool useU /* = true */)
{
    plString texName = (useU ? _TEMP_CONVERT_FROM_LITERAL("ALPHA_BLEND_FILTER_U2ALPHA_TRANS_64x4")
                             : _TEMP_CONVERT_FROM_LITERAL("ALPHA_BLEND_FILTER_V2ALPHA_TRANS_4x64") );

    int w = (useU ? 64 : 4);
    int h = (useU ? 4 : 64);

    // NOTE: CreateBlankMipmap might return an old mipmap if it was already created, so in those
    // cases we wouldn't really need to re-write the texture. However, there's no harm in doing so,
    // and since we're close to Alpha, I don't want to shake up the code any more than absolutely 
    // necessary. -mcn
    plMipmap *texture = plBitmapCreator::Instance().CreateBlankMipmap( w, h, plMipmap::kARGB32Config, 1, texName, node->GetLocation() );

    // set the color data
    uint32_t* pix = (uint32_t*)texture->GetImage();
    int x, y;
    for (y = 0; y < h; y++)
    {
        for (x = 0; x < w; x++)
        {
            // Filter either the U or V coordinate
            float shade = (useU ? (float)x / (w - 1) : (float)y / (h - 1));

            // These colors MUST be all white even though we're usuing the kBlendNoColor flag... 
            // Rad huh? Hooray for nVidia!!!
            *pix++ = MakeUInt32Color(1.0, 1.0, 1.0, shade);
        }
    }

    return texture;
}

//// IInsertSingleBlendLayer //////////////////////////////////////////////////
//  10.24.01 mcn - Added kMiscRestartPassHere to the layer below the inserted
//                 one, to guarantee that the hacked alpha layers draw in their
//                 own passes on the GF3. Keeping this here until we can properly
//                 handle the hack layers as piggybacks in the pipeline.

void hsMaterialConverter::IInsertSingleBlendLayer(plMipmap *texture, hsGMaterial *mat, 
                                                  plMaxNode *node, int layerIdx, int UVChan)
{
    // Need to tweak a few flags on its corresponding layer
    plLayer* underLay = plLayer::ConvertNoRef(mat->GetLayer(layerIdx - 1)->BottomOfStack());
    if( !underLay )
        return;

    // This error means a GeForce 1 or 2 won't do the vertex alpha correctly, and every other card
    // won't care. We've been ignoring this warning for years anyway, might as well just take it
    // out and stop interrupting the export.
//  fErrorMsg->Set((underLay->GetMiscFlags() & hsGMatState::kMiscBindNext) != 0, node->GetName(),
//      "Layer %s has its BindNext flag set, which can't be done with a vertex alpha blend. The "
//      "resulting material may not blend correctly.", underLay->GetKeyName()).Show();
    underLay->SetMiscFlags(underLay->GetMiscFlags() | hsGMatState::kMiscBindNext
                            | hsGMatState::kMiscRestartPassHere );
    underLay->SetBlendFlags(underLay->GetBlendFlags() | hsGMatState::kBlendAlpha);
    mat->SetCompositeFlags(mat->GetCompositeFlags() | hsGMaterial::kCompNeedsBlendChannel);

    
    plLayer* layer = new plLayer;
    layer->InitToDefault();
    hsgResMgr::ResMgr()->NewKey(underLay->GetKeyName() + _TEMP_CONVERT_FROM_LITERAL("_AlphaBlend"), layer, node->GetLocation());
    hsgResMgr::ResMgr()->AddViaNotify(texture->GetKey(), new plLayRefMsg(layer->GetKey(), plRefMsg::kOnCreate, 0, plLayRefMsg::kTexture), plRefFlags::kActiveRef);
    layer->SetAmbientColor(hsColorRGBA().Set(1.f, 1.f, 1.f, 1.f));
//  layer->SetZFlags(hsGMatState::kZNoZWrite | hsGMatState::kZIncLayer);
    // The inclayer prop probably wouldn't hurt here, because this layer should only get drawn as
    // an upper layer, but I'm nuking it out for consistency. mf
    layer->SetZFlags(hsGMatState::kZNoZWrite);
    uint32_t blendFlags = hsGMatState::kBlendNoTexColor | hsGMatState::kBlendAlphaMult | hsGMatState::kBlendAlpha;
    layer->SetBlendFlags(blendFlags);
    layer->SetClampFlags(hsGMatState::kClampTexture);
    layer->SetUVWSrc(UVChan);
    layer->SetMiscFlags(0);

    // Insert it in the right spot.
    hsgResMgr::ResMgr()->AddViaNotify(layer->GetKey(), new plMatRefMsg(mat->GetKey(), plRefMsg::kOnCreate, 
                                                                       layerIdx, plMatRefMsg::kLayer | plMatRefMsg::kInsert), plRefFlags::kActiveRef);
}

void hsMaterialConverter::IInsertAlphaBlendingLayers(Mtl *mtl, plMaxNode *node, hsGMaterial *mat, int UVChan,
                                                    hsBool makeAlphaLayer)
{
    if ((mat->GetLayer( 0 )->GetState().fBlendFlags & hsGMatState::kBlendAlpha) == 0 || UVChan < 0 || !makeAlphaLayer)
        return; // Not blending... false alarm... my bad... enjoy the buffet!

    if (!(UVChan < plGeometrySpan::kMaxNumUVChannels))
    {
        if (fErrorMsg->Set(!(fWarned & kWarnedTooManyUVs), node->GetName(), 
                        "Material is already using all available UV channels and thus doesn't have one for alpha "
                        "blending. Some layers will have incorrect channels assigned. (%s)", 
                        mtl->GetName()).CheckAskOrCancel() )
        fWarned |= kWarnedTooManyUVs; 
        UVChan = plGeometrySpan::kMaxNumUVChannels - 1;
    }
    int i;  

    hsGMaterial *objMat = mat;
    plMipmap *texture = IGetUVTransTexture(node);
    int origLayers = objMat->GetNumLayers();
    for (i = 0; i < origLayers; i++)
    {
        IInsertSingleBlendLayer(texture, objMat, node, 2 * i + 1, UVChan);
    }
}

void hsMaterialConverter::IInsertMultipassBlendingLayers(Mtl *mtl, plMaxNode *node, hsGMaterial *mat, int UVChan,
                                                        hsBool makeAlphaLayer)
{
    if (UVChan < 0 || !makeAlphaLayer)
        return; // Not blending, no layers to insert.

    if (!(UVChan < plGeometrySpan::kMaxNumUVChannels))
    {
        if (fErrorMsg->Set(!(fWarned & kWarnedTooManyUVs), node->GetName(), 
                        "Material is already using all available UV channels and thus doesn't have one for alpha "
                        "blending. Some layers will have incorrect channels assigned. (%s)", 
                        mtl->GetName()).CheckAskOrCancel() )
        fWarned |= kWarnedTooManyUVs; 
        UVChan = plGeometrySpan::kMaxNumUVChannels - 1;
    }

    int i;
    hsGMaterial *objMat = mat;
    IParamBlock2 *pb = mtl->GetParamBlockByID(plMultipassMtl::kBlkPasses);
    plMipmap *texture = IGetUVTransTexture(node);
    int currLayerNum = 0;
    for (i = 0; i < mtl->NumSubMtls(); i++)
    {
        if (!pb->GetInt(kMultOn, 0, i)) // Is the box for this submtl checked?
            continue; // No, skip it!

        if ((objMat->GetLayer(currLayerNum)->GetBlendFlags() & hsGMatState::kBlendAlpha) == 0) 
        {   // not alpha blending... skip!
            currLayerNum += pb->GetInt(kMultLayerCounts, 0, i);
            continue;
        }
        int j;

        for (j = 0; j < pb->GetInt(kMultLayerCounts, 0, i); j++)
        {
            IInsertSingleBlendLayer(texture, objMat, node, currLayerNum + 1, UVChan);
            currLayerNum += 2;
        }
    }
    return;
}

void hsMaterialConverter::IInsertCompBlendingLayers(Mtl *mtl, plMaxNode *node, hsGMaterial *mat, int subMtlFlags,
                                                   int UVChan, hsBool makeAlphaLayer)
{
    if (mat == nil)
        return;

    if (!IsCompositeMat(mtl))
    {
        hsAssert(false, "IInsertCompBlendingLayers() called on a non-composite material.");
        return;
    }

    int i;
    hsGMaterial *objMat = mat;
    plMipmap *textureU = IGetUVTransTexture(node, true);
    plMipmap *textureV = IGetUVTransTexture(node, false);
    IParamBlock2 *pb = mtl->GetParamBlockByID(plCompositeMtl::kBlkPasses);
    int currLayerNum = 0;
    int bitmask;
    hsBool firstUsedLayer = true;
    hsBool canWriteAlpha = ((plCompositeMtl *)mtl)->CanWriteAlpha() >= 0;
    plMipmap *currTexture;
    for (i = 0, bitmask = 0x1; i < mtl->NumSubMtls(); i++, bitmask <<= 1)
    {
        if ((bitmask & subMtlFlags) == 0) // skip it!
            continue;

        if (firstUsedLayer)
        {
            firstUsedLayer = false;
            if (i != 0) // it's not the base layer, so it must be covering the base layer up. Thus, it's
            {           // fully opaque, and we shouldn't blend.
                plLayer* underLay = plLayer::ConvertNoRef(objMat->GetLayer(currLayerNum)->BottomOfStack());
                if( underLay )
                    underLay->SetBlendFlags(underLay->GetBlendFlags() & ~hsGMatState::kBlendMask);
                currLayerNum += pb->GetInt(kCompLayerCounts, 0, i);
                continue;
            }
        }

        if (i == 0 && ((mat->GetLayer( 0 )->GetState().fBlendFlags & hsGMatState::kBlendAlpha) == 0))
        {
            currLayerNum += pb->GetInt(kCompLayerCounts, 0, i);
            continue;
        }
        
        int method = (i <= 0 ? kCompBlendVertexAlpha : pb->GetInt(kCompBlend, 0, i - 1));
        currTexture = (i == 1 ? textureU : textureV);
        int j;
        for (j = 0; j < pb->GetInt(kCompLayerCounts, 0, i); j++)
        {       
            if (!makeAlphaLayer && canWriteAlpha)
            {
                if (((plCompositeMtl *)mtl)->IsInverseBlend(method))
                {
                    plLayer* underLay = plLayer::ConvertNoRef(objMat->GetLayer(currLayerNum)->BottomOfStack());
                    underLay->SetBlendFlags(underLay->GetBlendFlags() | hsGMatState::kBlendInvertVtxAlpha);
                }
                currLayerNum++;
                continue;
            }
            
            IInsertSingleBlendLayer(currTexture, objMat, node, currLayerNum + 1, UVChan);
            currLayerNum += 2;
        }
    }
    return;
}

hsGMaterial *hsMaterialConverter::IProcessCompositeMtl(Mtl *mtl, plMaxNode *node, const plString &name, int UVChan, int subMtlFlags)
{
    if (!mtl || mtl->ClassID() != COMP_MTL_CLASS_ID)
        return nil;
    uint32_t *layerCounts = new uint32_t[mtl->NumSubMtls()];
    IParamBlock2 *pb = mtl->GetParamBlockByID(plCompositeMtl::kBlkPasses);
    hsGMaterial *mat = new hsGMaterial;
    hsgResMgr::ResMgr()->NewKey(plString::Format("%s_%d", name.c_str(), subMtlFlags), mat, node->GetLocation());
    
    int multiIndex = IFindSubIndex(node, mtl);
    hsBool needAlphaHack = node->AlphaHackLayersNeeded(multiIndex) > 0;
    hsBool canWriteAlpha = ((plCompositeMtl *)mtl)->CanWriteAlpha() >= 0;   

    int bitMask = 1;
    int i;
    for (i = 0; i < mtl->NumSubMtls(); i++)
    {
        Mtl *subMtl = nil;
        hsBool usingSubMtl = (i == 0 || pb->GetInt(kCompOn, 0, i - 1));
        if ((bitMask & subMtlFlags) != 0 && usingSubMtl)
        {
            plString pref = plString::Format("%s_%d", mat->GetKey()->GetName().c_str(), i);

            subMtl = mtl->GetSubMtl(i);
            if (subMtl != nil && subMtl->ClassID() == PASS_MTL_CLASS_ID)
                IProcessPlasmaMaterial(subMtl, node, mat, pref);
        }
        bitMask <<= 1;
        layerCounts[i] = mat->GetNumLayers();
        if (i > 0 && mat->GetNumLayers() > 0)
        {
            hsBool materialIsBad = false;
            int j;
            if (usingSubMtl)
            {
                for (j = mat->GetNumLayers() - 1; j >= (int)layerCounts[i - 1]; j--) 
                {
                    uint32_t blendFlags = mat->GetLayer(j)->GetBlendFlags();
                    if ((blendFlags & hsGMatState::kBlendMask) != hsGMatState::kBlendAlpha)
                    {
                        hsBool ignore = fErrorMsg->Set(!(fWarned & kWarnedCompMtlBadBlend), node->GetName(),
                            "For composite materials, all multi-layered submaterials (except the base)"
                            " must choose 'alpha' for 'output blending'. %s, which is a sub-material"
                            " of %s, doesn't and will not be included in the composite material.",
                            subMtl->GetName(), mtl->GetName()).CheckAskOrCancel();
                        
                        materialIsBad = true;
                        if (ignore)
                            fWarned |= kWarnedCompMtlBadBlend;
                    }

                    if ((needAlphaHack || !canWriteAlpha) &&
                        ((blendFlags & hsGMatState::kBlendAlphaMult) || (blendFlags & hsGMatState::kBlendAlphaAdd)))
                    {
                        hsBool ignore = fErrorMsg->Set(!(fWarned & kWarnedCompMtlBadBlend), node->GetName(),
                            "Composite material %s has a submaterial, %s, that requires too many textures in a single pass "
                            "(for blending effects). To cut this down, try some of the following:\n"
                            "1. Make sure all multi-layered submaterials (except the base)"
                            " choose 'alpha' for 'layer blending', and 'base alpha only' for 'layer alpha blending'\n"
                            "2. Force the object to be runtime lit.\n"
                            "3. Remove other components on this node that may add extra blending layers.\n"
                            "For now, the submaterial will not be included in the composite.",
                            mtl->GetName(), subMtl->GetName()).CheckAskOrCancel();

                        materialIsBad = true;
                        if (ignore)
                            fWarned |= kWarnedCompMtlBadBlend;
                    }
                }
            }
            if (materialIsBad) // Nuke all the layers of this sub material, so the artists don't just ignore the warnings
            {
                int min = (int)layerCounts[i - 1];
                for (j = mat->GetNumLayers() - 1; j >= min; j--)
                {
                    mat->GetKey()->Release(mat->GetLayer(j)->GetKey());
                }
                layerCounts[i] = mat->GetNumLayers();
            }
        }
    }

    for (i = mtl->NumSubMtls() - 1; i > 0; i--)
        layerCounts[i] -= layerCounts[i - 1];

    for (i = 0; i < mtl->NumSubMtls(); i++)
        pb->SetValue(kCompLayerCounts, 0, (int)layerCounts[i], i);

    delete [] layerCounts;
    if (mat->GetNumLayers() == 0)
    {
        // No one ever got a ref, so we ref and unref to make it go away.
        mat->GetKey()->RefObject();
        mat->GetKey()->UnRefObject();
        return IAddDefaultMaterial(node); 
    }

    return mat;
}

hsGMaterial *hsMaterialConverter::IProcessMultipassMtl(Mtl *mtl, plMaxNode *node, const plString &name, int UVChan)
{
    if (!mtl || mtl->ClassID() != MULTIMTL_CLASS_ID)
        return nil;

    hsGMaterial *mat = new hsGMaterial;
    uint32_t *layerCounts = new uint32_t[mtl->NumSubMtls()];
    hsgResMgr::ResMgr()->NewKey(name, mat, node->GetLocation());

    IParamBlock2 *pb = mtl->GetParamBlockByID(plMultipassMtl::kBlkPasses);
    for (int i = 0; i < mtl->NumSubMtls(); i++)
    {
        Mtl *subMtl = mtl->GetSubMtl(i);
        int check = pb->GetInt(kMultOn, 0, i);
        if ( ( subMtl->ClassID() == PASS_MTL_CLASS_ID ||
               subMtl->ClassID() == BUMP_MTL_CLASS_ID ) && check != 0)
        {
            IProcessPlasmaMaterial(subMtl, node, mat, mat->GetKey()->GetName());
        }
        layerCounts[i] = mat->GetNumLayers();
    }

    for (int i = mtl->NumSubMtls() - 1; i > 0; i--)
        layerCounts[i] -= layerCounts[i - 1];

    for (int i = 0; i < mtl->NumSubMtls(); i++)
        pb->SetValue(kMultLayerCounts, 0, (int)layerCounts[i], i);

    delete [] layerCounts;
    return mat;
}

hsGMaterial *hsMaterialConverter::IProcessParticleMtl(Mtl *mtl, plMaxNode *node, const plString &name)
{
    hsGuardBegin("hsMaterialConverter::IProcessParticleMaterial");

    plLocation nodeLoc = node->GetLocation(); 
    char* dbgNodeName = node->GetName();

    hsGMaterial *mat = new hsGMaterial;
    hsgResMgr::ResMgr()->NewKey(name, mat, nodeLoc);

    
    if(!mtl)
    {
        fErrorMsg->Set(!mtl, "Material Loading Error", "No material in ProcessParticleMaterial!").Show();
        fErrorMsg->Set();
        return nil;
    }

    plParticleMtl *particleMtl = (plParticleMtl *)mtl;
    IParamBlock2 *basicPB = mtl->GetParamBlockByID(plParticleMtl::kBlkBasic);
    //
    // Color / Opacity
    //
    Color amb  = basicPB->GetColor(plParticleMtl::kColorAmb);;
    Color dif  = basicPB->GetColor(plParticleMtl::kColor);
    float opac = float(basicPB->GetInt(plParticleMtl::kOpacity)) / 100.0f;

    fErrorMsg->Set(opac  < 0.0 || opac  > 1.0, dbgNodeName, "Bad opacity on mat %s", name.c_str()).CheckAndAsk();
    fErrorMsg->Set(dif.r < 0.0 || dif.r > 1.0, dbgNodeName, "Bad color (r) on mat %s", name.c_str()).CheckAndAsk();
    fErrorMsg->Set(dif.g < 0.0 || dif.g > 1.0, dbgNodeName, "Bad color (g) on mat %s", name.c_str()).CheckAndAsk();
    fErrorMsg->Set(dif.b < 0.0 || dif.b > 1.0, dbgNodeName, "Bad color (b) on mat %s", name.c_str()).CheckAndAsk();

    Color col = dif;

    //
    // Blend flags
    //
    uint32_t blendFlags = 0;

    //
    // Shade flags
    //
    uint32_t shadeFlags = 0;

    if (basicPB->GetInt(plParticleMtl::kNormal) == plParticleMtl::kEmissive)
        shadeFlags |= hsGMatState::kShadeEmissive;
    if( node->GetIsGUI() )
    {
        // We don't want materials on any GUI objects to *ever* be fogged 
        // (just in case we have some weird GUI particle system...)...4.25.2002 mcn
        shadeFlags |= hsGMatState::kShadeReallyNoFog;
    }

    //
    // Misc flags...
    //
    uint32_t miscFlags = 0;

    //
    // Z flags
    //
    uint32_t zFlags = 0;
    zFlags |= hsGMatState::kZNoZWrite;

    hsBool preserveUVOffset = false;//PreserveUVOffset(mtl);

    //
    // Process base layer
    //
    plLayer* baseLay = nil;
    Texmap *baseTex = basicPB->GetTexmap(plParticleMtl::kTexmap);
    if (baseTex && baseTex->ClassID() == LAYER_TEX_CLASS_ID)
    {
        plLayerInterface *layerIFace = plLayerConverter::Instance().ConvertTexmap( baseTex, node, 0, preserveUVOffset, false );

        baseLay = plLayer::ConvertNoRef(layerIFace->BottomOfStack());
        
        fErrorMsg->Set(!baseLay, node->GetName(), "Found no layer at end of IProcessPlasmaLayer()").Check();
            
        baseLay->SetBlendFlags(baseLay->GetBlendFlags() | blendFlags);
        baseLay->SetShadeFlags(baseLay->GetShadeFlags() | shadeFlags);
        baseLay->SetZFlags(baseLay->GetZFlags() | zFlags);
        baseLay->SetMiscFlags(baseLay->GetMiscFlags() | miscFlags); // 3.28.2001 mcn - Who didn't put this in?

        // No specular on particles. Spoot.
        baseLay->SetSpecularColor( hsColorRGBA().Set(0,0,0,1.f) );
        baseLay->SetSpecularPower( 0 );

        baseLay->SetAmbientColor(hsColorRGBA().Set(amb.r, amb.g, amb.b, 1.f));
        baseLay->SetRuntimeColor(hsColorRGBA().Set(col.r, col.g, col.b, 1.f));
        baseLay->SetPreshadeColor(hsColorRGBA().Set(0.f,0.f,0.f,1.f));
        baseLay->SetOpacity( opac );        // Don't scale the material color by this if we're add blending; do that later

        uint32_t blendType = 0;
        switch (basicPB->GetInt(plParticleMtl::kBlend))
        {
        case plParticleMtl::kBlendAlpha:        blendType |= hsGMatState::kBlendAlpha;  break;
        case plParticleMtl::kBlendAdd:          blendType |= hsGMatState::kBlendAdd;    break;
        }
        baseLay->SetBlendFlags(baseLay->GetBlendFlags() | blendType);

        plLayerInterface *layerAnim = layerIFace;

        IAddLayerToMaterial(mat, layerAnim);
    }


    return mat;
    hsGuardEnd; 
}

plLayerAnimation *IConvertNoteTrackAnims(plLayerAnimation *animLayer, SegmentMap *segMap, plMaxNode *node, 
                                         const plString &name)
{
    plLayerAnimation *prev = animLayer;
    
    for (SegmentMap::iterator it = segMap->begin(); it != segMap->end(); it++)
    {
        SegmentSpec *spec = it->second;
        if (spec->fType == SegmentSpec::kAnim)
        {
            plLayerAnimation *noteAnim = new plLayerAnimation;
            plString animName = plString::Format("%s_anim_%s", name.c_str(), spec->fName);
            hsgResMgr::ResMgr()->NewKey(animName, noteAnim, node->GetLocation());

            if (animLayer->GetPreshadeColorCtl())
                noteAnim->SetPreshadeColorCtl(animLayer->GetPreshadeColorCtl());
            if (animLayer->GetRuntimeColorCtl())
                noteAnim->SetRuntimeColorCtl(animLayer->GetRuntimeColorCtl());
            if (animLayer->GetAmbientColorCtl())
                noteAnim->SetAmbientColorCtl(animLayer->GetAmbientColorCtl());
            if (animLayer->GetSpecularColorCtl())
                noteAnim->SetSpecularColorCtl(animLayer->GetSpecularColorCtl());
            if (animLayer->GetOpacityCtl())
                noteAnim->SetOpacityCtl(animLayer->GetOpacityCtl());

            noteAnim->GetTimeConvert().SetBegin(spec->fStart);
            noteAnim->GetTimeConvert().SetEnd(spec->fEnd);

            noteAnim->AttachViaNotify(prev);
            prev = noteAnim;
        }
    }

    return prev;
}

void ISetDefaultAnim(plPassMtlBase* mtl, plAnimTimeConvert& tc, SegmentMap* segMap)
{
    plString animName = _TEMP_CONVERT_FROM_LITERAL( mtl->GetAnimName() );
    if( segMap && !animName.IsNull() && (segMap->find(animName) != segMap->end()) )
    {
        SegmentSpec *spec = (*segMap)[animName];
        tc.SetBegin(spec->fStart);
        tc.SetEnd(spec->fEnd);
    }

    if (mtl->GetAutoStart())
        tc.Start(0);
    else
        tc.Stop(true);

    if (mtl->GetLoop())
    {
        // Default to the entire anim
        float loopStart = tc.GetBegin();
        float loopEnd = tc.GetEnd();

        // If there's a loop, use it
        plString loopName = plString::FromUtf8(mtl->GetAnimLoopName());
        if (!loopName.IsEmpty() && segMap)
            GetSegMapAnimTime(loopName, segMap, SegmentSpec::kLoop, loopStart, loopEnd);

        tc.SetLoopPoints(loopStart, loopEnd);
        tc.Loop(true);
    }
    else
        tc.Loop(false);
}

void StuffStopPoints(SegmentMap *segMap, hsTArray<float> &out)
{
    if (segMap == nil)
        return;

    for (SegmentMap::iterator it = segMap->begin(); it != segMap->end(); it++)
    {
        SegmentSpec *spec = it->second;
        if (spec->fType == SegmentSpec::kStopPoint)
        {
            out.Append(spec->fStart);
        }
    }
}

void IProcessMtlAnimEase(plPassMtlBase *mtl, plAnimTimeConvert &tc)
{
    if (mtl->GetEaseInType() != plAnimEaseTypes::kNoEase)
    {
        tc.SetEase(true, mtl->GetEaseInType(), mtl->GetEaseInMinLength(),
                   mtl->GetEaseInMaxLength(), mtl->GetEaseInNormLength());
    }
    if (mtl->GetEaseOutType() != plAnimEaseTypes::kNoEase)
    {
        tc.SetEase(false, mtl->GetEaseOutType(), mtl->GetEaseOutMinLength(),
                   mtl->GetEaseOutMaxLength(), mtl->GetEaseOutNormLength());
    }
}
/*
hsBool hsMaterialConverter::CheckValidityOfSDLVarAnim(plPassMtlBase *mtl, char *varName, plMaxNode *node)
{
    plStateDescriptor *sd = nil;
    char *ageName;
    ageName = node->GetAgeName();
    if (ageName)
        sd = plSDLMgr::GetInstance()->FindDescriptor(ageName, plSDL::kLatestVersion);
    if (sd)
    {
        plVarDescriptor *var = sd->FindVar(varName);
        if (var == nil)
        {
            char buff[512];
            sprintf(buff, "Cannot find variable named \"%s\" used to animate the material "
                    "\"%s\". This material won't be animated.", varName, mtl->GetName());
            if (fErrorMsg->Set(!(fWarned & kWarnedBadAnimSDLVarName), node->GetName(), buff).CheckAskOrCancel() )
                fWarned |= kWarnedBadAnimSDLVarName;
            return false;
        }
        if (!(var->GetType() == plVarDescriptor::kFloat ||
              var->GetType() == plVarDescriptor::kDouble ||
              var->GetType() == plVarDescriptor::kTime ||
              var->GetType() == plVarDescriptor::kAgeTimeOfDay))
        {
            char buff[512];
            sprintf(buff, "Material \"%s\" animates on the age variable \"%s\", which is "
                    "of type \"%s\". Only the types float/double/time/ageTimeOfDay may be used. "
                    "Ignoring the animation of this material.", mtl->GetName(), varName, var->GetTypeString());
            if (fErrorMsg->Set(!(fWarned & kWarnedBadAnimSDLVarName), node->GetName(), buff).CheckAskOrCancel())
                fWarned |= kWarnedBadAnimSDLVarName;
            return false;
        }
        return true;
    }
    return false;
}
*/

static plAnimStealthNode* IGetEntireAnimation(plPassMtlBase* mtl)
{
    const int count = mtl->GetNumStealths();
    int i;
    for( i = 0; i < count; i++ )
    {
        plAnimStealthNode* stealth = mtl->GetStealth(i);

        plString segName = stealth->GetSegmentName();
        if( segName.IsEmpty() || !segName.Compare(ENTIRE_ANIMATION_NAME, plString::kCaseInsensitive) )
            return stealth;

    }
    return nil;
}

static plLayerInterface* IProcessLayerMovie(plPassMtlBase* mtl, plLayerTex* layTex, plMaxNode* node, 
                                         plLayerInterface* layerIFace)
{
    IParamBlock2* bitmapPB = layTex->GetParamBlockByID( plLayerTex::kBlkBitmap );
    if( !bitmapPB )
        return layerIFace;

    PBBitmap    *pbbm = nil;

    if( !bitmapPB->GetInt(kBmpUseBitmap) || !(pbbm = bitmapPB->GetBitmap(kBmpBitmap)) )
        return layerIFace;

    BitmapInfo  *bi = &pbbm->bi;
    if( !bi || !bi->Name() || !*bi->Name() )
        return layerIFace;

    const char* fileName = bi->Name();

    plAnimStealthNode* stealth = IGetEntireAnimation(mtl);

    const char* ext = plFileUtils::GetFileExt(fileName);
    bool isBink = ext && (stricmp(ext, "bik") == 0);
    bool isAvi  = ext &&(stricmp(ext, "avi") == 0);

    if (isBink || isAvi)
    {
        char movieName[256];
        sprintf(movieName, "avi/%s", plFileUtils::GetFileName(fileName));

        plLayerMovie* movieLayer = nil;
        plString moviePostfix;

        if (isBink)
        {
            movieLayer = new plLayerBink;
            moviePostfix = _TEMP_CONVERT_FROM_LITERAL("_bink");
        }
        else if (isAvi)
        {
            movieLayer = new plLayerAVI;
            moviePostfix = _TEMP_CONVERT_FROM_LITERAL("_avi");
        }

        plString movieKeyName = layerIFace->GetKeyName() + moviePostfix;
        hsgResMgr::ResMgr()->NewKey(movieKeyName, movieLayer, node->GetLocation());

        movieLayer->SetMovieName(movieName);
        movieLayer->Eval(0,0,0);

        plAnimTimeConvert& tc = movieLayer->GetTimeConvert();

        if (stealth)
        {
            if (stealth->GetAutoStart())
                tc.Start(0);
            else
                tc.Stop(true);

            tc.Loop(stealth->GetLoop());
        }
        tc.SetLoopPoints(0, movieLayer->GetLength());
        tc.SetBegin(0);
        tc.SetEnd(movieLayer->GetLength());

        movieLayer->AttachViaNotify(layerIFace);
        layerIFace = movieLayer;
    }

    return layerIFace;
}

plLayerInterface* IProcessLayerAnimation(plPassMtlBase* mtl, plLayerTex* layTex, plMaxNode* node, 
                                         const plString &name, plLayerInterface* layerIFace)
{
    hsControlConverter& cc = hsControlConverter::Instance();
    
    //
    // Look for animations. These will get tacked onto the base pass layer
    StdUVGen *uvGen = (StdUVGen*)layTex->GetTheUVGen();
    plLeafController* xfmCtl = cc.MakeMatrix44Controller(uvGen, node->GetName());

    if( !xfmCtl )
        return layerIFace;

    if( mtl->GetUseGlobal() )
    {
        plLayerSDLAnimation *SDLLayer = new plLayerSDLAnimation;
        plString animName = plString::Format("%s_anim_%s", name.c_str(), mtl->GetGlobalVarName());
        hsgResMgr::ResMgr()->NewKey(animName, SDLLayer, node->GetLocation());

        SDLLayer->SetVarName((char*)mtl->GetGlobalVarName());
        SDLLayer->SetTransformCtl(xfmCtl);
        SDLLayer->AttachViaNotify(layerIFace);
        node->CheckSynchOptions(SDLLayer);
        
        return SDLLayer;
    }   

    // Don't need anymore, really was just for validation above
    delete xfmCtl;

    // If no valid animation track is chosen, return entire animation.
    int i, count = mtl->GetNumStealths();
    for( i = count - 1; i >= 0; i-- )
    {
        plAnimStealthNode *stealth = mtl->GetStealth( i );

        plLayerAnimation *noteAnim = new plLayerAnimation;
        node->CheckSynchOptions(noteAnim);

        plString segName = stealth->GetSegmentName();
        bool isDefault = ( segName.IsNull() || segName.Compare( ENTIRE_ANIMATION_NAME ) == 0 ) ? true : false;

        plString animName = name + ( ( isDefault ) ? _TEMP_CONVERT_FROM_LITERAL( "_LayerAnim_" )
                                 : ( _TEMP_CONVERT_FROM_LITERAL("_LayerAnim") + segName ) );
        hsgResMgr::ResMgr()->NewKey( animName, noteAnim, node->GetLocation() );

        StdUVGen *uvGen = (StdUVGen *)layTex->GetTheUVGen();
        plLeafController *segXfmCtl = cc.MakeMatrix44Controller( uvGen, node->GetName() );
        noteAnim->SetTransformCtl( segXfmCtl );

        // ATC conversion stuff
        stealth->StuffToTimeConvert( noteAnim->GetTimeConvert(), segXfmCtl->GetLength() );
        
        // Set segment name if we're not the default
        if( !isDefault )
            noteAnim->SetSegmentID( segName );

        // And attach!
        noteAnim->AttachViaNotify( layerIFace );

        // So the next time will attach to this layer...
        layerIFace = noteAnim;
    }

    return layerIFace;

}

plLayerInterface* IProcessAnimation(plPassMtlBase *mtl, plMaxNode *node, const plString &name,
                                    plLayerInterface *layerIFace)
{
    hsControlConverter& cc = hsControlConverter::Instance();

    float maxLength = 0;
    //
    // Look for animations. These will get tacked onto the base pass layer
    Control *maxColCtl = mtl->GetPreshadeColorController();
    plController *colCtl = cc.MakeColorController(maxColCtl, node);
    
    Control *maxRunColCtl = nil;
    plController *runColCtl = nil;
    if( mtl->GetDiffuseColorLock() )
        maxRunColCtl = maxColCtl;
    else
        maxRunColCtl = mtl->GetRuntimeColorController();

    runColCtl = cc.MakeColorController(maxRunColCtl, node);

    Control *maxAmbCtl = mtl->GetAmbColorController();
    plController *ambCtl = cc.MakeColorController(maxAmbCtl, node);

    Control *maxOpaCtl = mtl->GetOpacityController();
    plLeafController *opaCtl = cc.MakeScalarController(maxOpaCtl, node);

    Control *maxSpecCtl = mtl->GetSpecularColorController();
    plController *specCtl = cc.MakeColorController(maxSpecCtl, node);

    // If there are no animations, return
    if (!colCtl && !ambCtl && !opaCtl && !runColCtl && !specCtl)
        return layerIFace;
    if( colCtl )
        maxLength = colCtl->GetLength();
    if( ambCtl && (ambCtl->GetLength() > maxLength) )
        maxLength = ambCtl->GetLength();
    if( opaCtl && (opaCtl->GetLength() > maxLength) )
        maxLength = opaCtl->GetLength();
    if( runColCtl && (runColCtl->GetLength() > maxLength) )
        maxLength = runColCtl->GetLength();
    if( specCtl && (specCtl->GetLength() > maxLength) )
        maxLength = specCtl->GetLength();

    if( mtl->GetUseGlobal() )
    {
        //if (!hsMaterialConverter::Instance().CheckValidityOfSDLVarAnim(mtl, mtl->GetGlobalVarName(), node))
        //  return layerIFace;
        
        plLayerSDLAnimation *SDLLayer = new plLayerSDLAnimation;
        plString animName = plString::Format("%s_anim_%s", name.c_str(), mtl->GetGlobalVarName());
        hsgResMgr::ResMgr()->NewKey(animName, SDLLayer, node->GetLocation());

        SDLLayer->SetVarName((char*)mtl->GetGlobalVarName());
        node->CheckSynchOptions(SDLLayer);

        if (colCtl)
            SDLLayer->SetPreshadeColorCtl(colCtl);
        if (ambCtl)
            SDLLayer->SetAmbientColorCtl(ambCtl);
        if (opaCtl)
            SDLLayer->SetOpacityCtl(opaCtl);
        if( runColCtl )
            SDLLayer->SetRuntimeColorCtl( runColCtl );
        if( specCtl )
            SDLLayer->SetSpecularColorCtl( specCtl );

        SDLLayer->AttachViaNotify(layerIFace);

        return SDLLayer;
    }

    // Delete these, since they're no longer needed
    delete colCtl;
    delete ambCtl;
    delete opaCtl;
    delete runColCtl;
    delete specCtl;

    // Loop through the stealths, since each one represents a segment
    int i, count = mtl->GetNumStealths();
    for( i = count - 1; i >= 0; i-- )
    {
        plAnimStealthNode *stealth = mtl->GetStealth( i );

        plLayerAnimation *noteAnim = new plLayerAnimation;
        node->CheckSynchOptions(noteAnim);

        plString segName = stealth->GetSegmentName();
        bool isDefault = ( segName.IsNull() || segName.Compare( ENTIRE_ANIMATION_NAME ) == 0 ) ? true : false;

        plString animName = name + ( ( isDefault ) ? _TEMP_CONVERT_FROM_LITERAL( "_anim" )
                                 : ( _TEMP_CONVERT_FROM_LITERAL("_anim_") + segName ) );
        hsgResMgr::ResMgr()->NewKey( animName, noteAnim, node->GetLocation() );

        plController *noteColCtl = cc.MakeColorController( maxColCtl, node );
        plController *noteAmbCtl = cc.MakeColorController( maxAmbCtl, node );
        plController *noteOpaCtl = cc.MakeScalarController( maxOpaCtl, node );
        plController *noteSpecCtl = cc.MakeColorController( maxSpecCtl, node );
        plController *noteRunColCtl = cc.MakeColorController( maxColCtl, node );

        if( noteColCtl )
            noteAnim->SetPreshadeColorCtl( noteColCtl );
        if( noteAmbCtl )
            noteAnim->SetAmbientColorCtl( noteAmbCtl );
        if( noteOpaCtl )
            noteAnim->SetOpacityCtl( noteOpaCtl );
        if( noteRunColCtl )
            noteAnim->SetRuntimeColorCtl( noteRunColCtl );
        if( noteSpecCtl )
            noteAnim->SetSpecularColorCtl( noteSpecCtl );

        // ATC conversion stuff
        stealth->StuffToTimeConvert( noteAnim->GetTimeConvert(), maxLength );
        
        // Set segment name if we're not the default
        if( !isDefault )
            noteAnim->SetSegmentID( segName );

        // And attach!
        noteAnim->AttachViaNotify( layerIFace );

        // So the next time will attach to this layer...
        layerIFace = noteAnim;
    }

    // Cleanup
    return layerIFace;

}


hsBool hsMaterialConverter::IHasSubMtl(Mtl* base, Mtl* sub)
{
    if( !(base && sub) )
        return false;

    if( base == sub )
        return true;

    int nSub = base->NumSubMtls();
    int i;
    for( i = 0; i < nSub; i++ )
    {
        if( IHasSubMtl(base->GetSubMtl(i), sub) )
            return true;
    }
    return false;
}

int hsMaterialConverter::IFindSubIndex(plMaxNode* node, Mtl* mtl)
{
    Mtl* baseMtl = node->GetMtl();
    if( !IsMultiMat(baseMtl) )
        return 0;

    int i;
    for( i = 0; i < mtl->NumSubMtls(); i++ )
    {
        if( IHasSubMtl(mtl->GetSubMtl(i), mtl) )
            return i;
    }
    return -1;
}

//
// Converters for different kinds of materials (Plasma, Environ, Standard)
//

// Now handles both plPassMtl and plDecalMtl which derive from plPassMtlBase
hsBool hsMaterialConverter::IProcessPlasmaMaterial(Mtl *mtl, plMaxNode *node, hsGMaterial *mat, const plString& name)
{
    hsGuardBegin("hsMaterialConverter::IProcessPlasmaMaterial");

    plLocation nodeLoc= node->GetLocation(); 
    char* dbgNodeName = node->GetName();
    
    int initNumLayers = mat->GetNumLayers();

    if(!mtl)
    {
        fErrorMsg->Set(!mtl, "Material Loading Error", "No material in ProcessPlasmaMaterial!").Show();
        //hsAssert(mtl, "No material in ProcessPlasmaMaterial!");

        fErrorMsg->Set();
        return false;
    }

    int multiIndex = IFindSubIndex(node, mtl);
    hsBool needAlphaHack = node->AlphaHackLayersNeeded(multiIndex) > 0;

    plPassMtlBase *passBase = (plPassMtlBase *)mtl;
    //
    // Color / Opacity
    //
    Color amb  = passBase->GetAmbColor();
    Color dif  = passBase->GetColor();
    Color runDif = passBase->GetRuntimeColor();
    if( passBase->GetDiffuseColorLock() )
        runDif = dif;

    float opac = float(passBase->GetOpacity()) / 100.0f;

    fErrorMsg->Set(opac  < 0.0 || opac  > 1.0, dbgNodeName, "Bad opacity on mat %s", name.c_str()).CheckAndAsk();
    fErrorMsg->Set(dif.r < 0.0 || dif.r > 1.0, dbgNodeName, "Bad color (r) on mat %s", name.c_str()).CheckAndAsk();
    fErrorMsg->Set(dif.g < 0.0 || dif.g > 1.0, dbgNodeName, "Bad color (g) on mat %s", name.c_str()).CheckAndAsk();
    fErrorMsg->Set(dif.b < 0.0 || dif.b > 1.0, dbgNodeName, "Bad color (b) on mat %s", name.c_str()).CheckAndAsk();
    fErrorMsg->Set(runDif.r < 0.0 || runDif.r > 1.0, dbgNodeName, "Bad runtime color (r) on mat %s", name.c_str()).CheckAndAsk();
    fErrorMsg->Set(runDif.g < 0.0 || runDif.g > 1.0, dbgNodeName, "Bad runtime color (g) on mat %s", name.c_str()).CheckAndAsk();
    fErrorMsg->Set(runDif.b < 0.0 || runDif.b > 1.0, dbgNodeName, "Bad runtime color (b) on mat %s", name.c_str()).CheckAndAsk();

//  Color col = dif - amb;
    Color col = dif;

    if( passBase->GetDiffuseColorLock() )
        runDif = dif;

    // If we've got No Preshading explicitly set, then set the 
    // PreshadeColor to black (so anything in the vertex color
    // will get ignored at runtime).
    if( node->GetRunTimeLight() )
        col.Black();
    //
    // Blend flags
    //
    uint32_t blendFlags = 0;
    if( node->GetAlphaTestHigh() || passBase->GetAlphaTestHigh() )
        blendFlags |= hsGMatState::kBlendAlphaTestHigh;

    // Z Flag
    if (passBase->GetZOnly())
        blendFlags |= hsGMatState::kBlendNoColor;

    //
    // Shade flags
    //
    uint32_t shadeFlags = 0;
    if (passBase->GetSoftShadow())
        shadeFlags |= hsGMatState::kShadeSoftShadow;
    if (passBase->GetNoProj())
        shadeFlags |= hsGMatState::kShadeNoProjectors;
    if (passBase->GetVertexShade())
        shadeFlags |= hsGMatState::kShadeVertexShade;
    if (passBase->GetNoShade())
        shadeFlags |= hsGMatState::kShadeNoShade;
    if (passBase->GetNoFog())
        shadeFlags |= hsGMatState::kShadeReallyNoFog;
    if (passBase->GetWhite())
        shadeFlags |= hsGMatState::kShadeWhite;
    /// Emissive flag, from basic parameters
    if( passBase->GetEmissive() )
    {
        shadeFlags |= hsGMatState::kShadeEmissive;
        col.r = col.g = col.b = 0.0f;
        runDif = col;
    }
    if( node->GetIsGUI() )
    {
        // We don't want materials on any GUI objects to *ever* be fogged...4.25.2002 mcn
        shadeFlags |= hsGMatState::kShadeReallyNoFog;
    }

    // Specular flags
    float   shine   = 0.0;
    Color   specColor = Color(0,0,0);

    if (passBase->GetUseSpec())
    {
        shadeFlags |= hsGMatState::kShadeSpecular;
        shine = passBase->GetShine();
        specColor = passBase->GetSpecularColor();
    }

    //
    // Misc flags...
    //
    uint32_t miscFlags = 0;
    if (passBase->GetBasicWire())
        miscFlags |= hsGMatState::kMiscWireFrame;
    if (passBase->GetMeshOutlines())
        miscFlags |= hsGMatState::kMiscDrawMeshOutlines;
    if( !node->GetDup2Sided() && passBase->GetTwoSided() )
        miscFlags |= hsGMatState::kMiscTwoSided;

    //
    // Z flags
    //
    uint32_t zFlags = 0;
    if (passBase->GetZClear())
        zFlags |= hsGMatState::kZClearZ;
    if (passBase->GetZNoRead())
        zFlags |= hsGMatState::kZNoZRead;
    if (passBase->GetZNoWrite())
        zFlags |= hsGMatState::kZNoZWrite;
    if (passBase->GetZInc())
        zFlags |= hsGMatState::kZIncLayer;
    
    // Decal flag settings
    if (IsDecalMat(mtl)) 
    {
        zFlags |= hsGMatState::kZIncLayer | hsGMatState::kZNoZWrite;
        mat->SetCompositeFlags(mat->GetCompositeFlags() | hsGMaterial::kCompDecal);
    }   


    // We're not using specular highlights?
    if (shadeFlags & hsGMatState::kShadeSpecularHighlight)
    {
        shadeFlags &= ~(hsGMatState::kShadeSpecular 
                        | hsGMatState::kShadeSpecularHighlight 
                        | hsGMatState::kShadeSpecularAlpha 
                        | hsGMatState::kShadeSpecularColor);
    }

    hsBool preserveUVOffset = PreserveUVOffset(mtl);

    //
    // Process base layer
    //
    plLayer* baseLay = nil;
    Texmap *baseTex = passBase->GetBaseLayer();
    plPlasmaMAXLayer *plasmaLayer;
    if (baseTex && ( plasmaLayer = plPlasmaMAXLayer::GetPlasmaMAXLayer( baseTex ) ) != nil )
    {
        plLayerInterface *layerIFace = plLayerConverter::Instance().ConvertTexmap( baseTex, node, 0, preserveUVOffset, false ); 

        baseLay = plLayer::ConvertNoRef(layerIFace->BottomOfStack());
        
        fErrorMsg->Set(!baseLay, node->GetName(), "Found no layer at end of IProcessPlasmaLayer()").Check();
            
        // This sucks. If we're adding a base layer on top of layers whose emissive flags don't match,
        // we have to set kMiscRestartPassHere on this layer
        if( mat->GetNumLayers() > 0 )
        {
            uint32_t  lastShades = mat->GetLayer( mat->GetNumLayers() - 1 )->GetShadeFlags();

            if( ( lastShades ^ shadeFlags ) & hsGMatState::kShadeEmissive )
                baseLay->SetMiscFlags( baseLay->GetMiscFlags() | hsGMatState::kMiscRestartPassHere );
        }

        baseLay->SetBlendFlags(baseLay->GetBlendFlags() | blendFlags);
        baseLay->SetShadeFlags(baseLay->GetShadeFlags() | shadeFlags);
        baseLay->SetZFlags(baseLay->GetZFlags() | zFlags);
        baseLay->SetMiscFlags(baseLay->GetMiscFlags() | miscFlags); // 3.28.2001 mcn - Who didn't put this in?

        if (baseLay->GetShadeFlags() & hsGMatState::kShadeSpecular) 
        {
            baseLay->SetSpecularColor( hsColorRGBA().Set( specColor.r, specColor.g, specColor.b, 1.f ) );
            baseLay->SetSpecularPower(shine);
        }

        baseLay->SetAmbientColor(hsColorRGBA().Set(amb.r, amb.g, amb.b, 1.f));
        baseLay->SetPreshadeColor(hsColorRGBA().Set(col.r, col.g, col.b, 1.f));
        baseLay->SetRuntimeColor(hsColorRGBA().Set(runDif.r, runDif.g, runDif.b, 1.f));
        baseLay->SetOpacity( opac );        // Don't scale the material color by this if we're add blending; do that later

        uint32_t blendType = 0;
        switch (passBase->GetOutputBlend())
        {
        case plPassMtlBase::kBlendAlpha:        blendType |= hsGMatState::kBlendAlpha;  break;
        case plPassMtlBase::kBlendAdd:          blendType |= hsGMatState::kBlendAdd;    break;
        }
        baseLay->SetBlendFlags(baseLay->GetBlendFlags() | blendType);
        if( (opac < 1.f) && (blendType & hsGMatState::kBlendAlpha) )
            baseLay->SetMiscFlags( baseLay->GetMiscFlags() | hsGMatState::kMiscRestartPassHere );

        plLayerInterface *layerAnim = IProcessAnimation(passBase, node, layerIFace->GetKeyName(), layerIFace);

        layerAnim = IProcessLayerAnimation(passBase, (plLayerTex*)baseTex, node, layerIFace->GetKeyName(), layerAnim);

        layerAnim = IProcessLayerMovie(passBase, (plLayerTex*)baseTex, node, layerAnim);

        layerAnim->Eval(0,0,0);

        IAddLayerToMaterial(mat, layerAnim);

    }

    // If we don't have a base layer, there's not much left to talk about.
    if( !baseLay )
        return true;

    if( baseLay && ( IsBumpLayer(baseTex) || IsBumpMtl( mtl ) ) )
        baseLay->SetMiscFlags(baseLay->GetMiscFlags() | hsGMatState::kMiscBumpLayer);
    if( baseLay && node->GetWaterDecEnv() )
        baseLay->SetShadeFlags(baseLay->GetShadeFlags() | hsGMatState::kShadeEnvironMap);

    // If the top layer is on, and there is a valid base layer
    if (passBase->GetTopLayerOn() && baseLay)
    {
        Texmap *texMap = passBase->GetTopLayer();
        plPlasmaMAXLayer *plasmaTopLayer;
        if (texMap && ( plasmaTopLayer = plPlasmaMAXLayer::GetPlasmaMAXLayer( baseTex ) ) != nil )
        {
            // Blend flags (do this first so we can pass it to IProcessPlasmaLayer())
            uint32_t blendIndex = passBase->GetLayerBlend();
            if( needAlphaHack
                &&(passBase->GetOutputBlend() == plPassMtlBase::kBlendAlpha)
                &&(blendIndex == plPassMtlBase::kBlendAdd || blendIndex == plPassMtlBase::kBlendMult) )
            {
                blendIndex = plPassMtlBase::kBlendAlpha;
                if (fErrorMsg->Set(!(fWarned & kWarnedAlphaAddCombo), node->GetName(), 
                                   "Material \"%s\" has Output Blending set to Alpha, but Layer Blending is not. "
                                   "This combination requires RunTime Lighting or give up vertex alpha and opacity animation."
                                   "Using a layer blend of alpha for now.", 
                                   passBase->GetName()).CheckAskOrCancel() )
                    fWarned |= kWarnedAlphaAddCombo;
            }

            uint32_t blendType = 0;
            switch (blendIndex)
            {
            case plPassMtlBase::kBlendAlpha:        
                blendType |= hsGMatState::kBlendAlpha;  
                break;
            case plPassMtlBase::kBlendAdd:          
                blendType |= hsGMatState::kBlendAdd;    
                break;
            case plPassMtlBase::kBlendMult:     
                blendType |= hsGMatState::kBlendMult;   
                break;
            }

            plLayerInterface *layerIFace = plLayerConverter::Instance().ConvertTexmap( texMap, node, blendType, preserveUVOffset, true ); 

            if( layerIFace != nil )
            {
                if( (baseLay->GetBlendFlags() | layerIFace->GetBlendFlags()) & (hsGMatState::kBlendNoTexColor | hsGMatState::kBlendNoTexAlpha) )
                    baseLay->SetMiscFlags(baseLay->GetMiscFlags() | hsGMatState::kMiscBindNext);

                if( blendIndex == plPassMtlBase::kBlendMult )
                    baseLay->SetMiscFlags(baseLay->GetMiscFlags() | hsGMatState::kMiscBindNext);

                plLayer* hLay = plLayer::ConvertNoRef(layerIFace->BottomOfStack());

                fErrorMsg->Set(!hLay, node->GetName(), "Found no layer at end of IProcessPlasmaLayer()").Check();

                hLay->SetBlendFlags(hLay->GetBlendFlags() | blendType | blendFlags );
                hLay->SetShadeFlags(hLay->GetShadeFlags() | shadeFlags);
                // 5.29.2001 mcn - Upper layers are *supposed* to have this set on them
//              hLay->SetZFlags( hLay->GetZFlags() | zFlags | hsGMatState::kZNoZWrite | hsGMatState::kZIncLayer );
                // 2/28 2002 - mf - upper layers don't need ZIncLayer (because we no longer clip them).
                hLay->SetZFlags( hLay->GetZFlags() | zFlags | hsGMatState::kZNoZWrite );

                if (hLay->GetShadeFlags() & hsGMatState::kShadeSpecular) 
                {
                    hLay->SetSpecularColor( hsColorRGBA().Set( specColor.r, specColor.g, specColor.b, 1.f ) );
                    hLay->SetSpecularPower(shine);
                }

                hLay->SetAmbientColor(hsColorRGBA().Set(amb.r, amb.g, amb.b, 1.f));
                hLay->SetPreshadeColor(hsColorRGBA().Set(col.r, col.g, col.b, 1.f));
                hLay->SetRuntimeColor(hsColorRGBA().Set(runDif.r, runDif.g, runDif.b, 1.f));
                hLay->SetOpacity( opac );

                if( IsBumpLayer(texMap) || IsBumpMtl( mtl ) )
                    hLay->SetMiscFlags(hLay->GetMiscFlags() | hsGMatState::kMiscBumpLayer);

                hsBool canFunk = !needAlphaHack;
                // Base layer fixups
                // Yet another attempt to protect the artists from themselves. Setting the 
                // alpha combine to mult or add, when the output blend is opaque is at best meaningless
                // and can be dangerous. Just ignore them.
                if( baseLay->GetBlendFlags() & hsGMatState::kBlendAlpha )
                {
                    switch (passBase->GetOutputAlpha())
                    {
                    case plPassMtlBase::kAlphaMultiply:
                        hLay->SetBlendFlags(hLay->GetBlendFlags() | hsGMatState::kBlendAlphaMult);
                        baseLay->SetMiscFlags(baseLay->GetMiscFlags() | hsGMatState::kMiscBindNext);
                        canFunk = false;
                        break;
                    case plPassMtlBase::kAlphaAdd:
                        hLay->SetBlendFlags(hLay->GetBlendFlags() | hsGMatState::kBlendAlphaAdd);
                        baseLay->SetMiscFlags(baseLay->GetMiscFlags() | hsGMatState::kMiscBindNext);
                        canFunk = false;
                        break;
                    }
                }

                if( canFunk && IHasFunkyOpacity(node, baseTex) )
                {
                    IAppendFunkyLayer(node, baseTex, mat);
                    // warn if requested but can't?
                }

                layerIFace = IProcessLayerAnimation(passBase, (plLayerTex*)texMap, node, layerIFace->GetKeyName(), layerIFace);

                layerIFace = IProcessLayerMovie(passBase, (plLayerTex*)baseTex, node, layerIFace);

                layerIFace->Eval(0,0,0);

                IAddLayerToMaterial(mat, layerIFace);

                if( canFunk && IHasFunkyOpacity(node, texMap) )
                {
                    IAppendFunkyLayer(node, texMap, mat);
                    // warn if requested but can't?
                }
            }
        }
    }
    else
    {
        if( IHasFunkyOpacity(node, baseTex) )
        {
            if( !needAlphaHack )
            {
                IAppendFunkyLayer(node, baseTex, mat);
            }
            else
            {
                // warn?
            }
        }
    }

    return true;
    hsGuardEnd; 
}

void hsMaterialConverter::IAddLayerToMaterial(hsGMaterial *mat, plLayerInterface *layer)
{
    plMatRefMsg* msg = new plMatRefMsg(mat->GetKey(), plRefMsg::kOnCreate, -1, plMatRefMsg::kLayer);
    hsgResMgr::ResMgr()->AddViaNotify(layer->GetKey(), msg, plRefFlags::kActiveRef);
}

//
// Functions called by the converters up above...
//
#include "MaxPlasmaMtls/Layers/plLayerTex.h"

//// IMustBeUniqueMaterial ////////////////////////////////////////////////////
//  Fun stuff here. If one of the layers of the material is a dynamic EnvMap,
//  and its anchor is set to nil (i.e. "self"), then the layer must be unique
//  for each object its applied to. However, that means the material must
//  *ALSO* be unique. So this function will tell us whether we have to make
//  a unique material.

hsBool  hsMaterialConverter::IMustBeUniqueMaterial( Mtl *mtl )
{
    hsGuardBegin( "hsMaterialConverter::IMustBeUniqueMaterial" );

    if( !mtl )
        return false;

    const char  *dbgName = mtl->GetName();

    if( IsMultiMat( mtl ) || IsMultipassMat( mtl ) || IsCompositeMat( mtl ) )
    {
        int iMtl;
        for( iMtl = 0; iMtl < mtl->NumSubMtls(); iMtl++ )
        {
            if( IMustBeUniqueMaterial( mtl->GetSubMtl( iMtl ) ) ) 
                return true;
        }
        return false;
    }
    else if( IsParticleMat( mtl ) )
        return false;
    else if( mtl->ClassID() == PASS_MTL_CLASS_ID )
    { 
        // It is std material.  does not have any submaterials
        plPassMtlBase       *passMtl = (plPassMtlBase *)mtl;

        if( IMustBeUniqueLayer( passMtl->GetBaseLayer() ) )
            return true;

        if( passMtl->GetTopLayerOn() && passMtl->GetTopLayer() && 
            IMustBeUniqueLayer( passMtl->GetTopLayer() ) )
            return true;

        return false;
    }
    
    return false;

    hsGuardEnd; 
}

//// IMustBeUniqueLayer ///////////////////////////////////////////////////////
//  Check for a single layer (see IMustBeUniqueMaterial())

hsBool  hsMaterialConverter::IMustBeUniqueLayer( Texmap *layer )
{
    plPlasmaMAXLayer    *plasmaLayer = plPlasmaMAXLayer::GetPlasmaMAXLayer( layer );
    if( plasmaLayer != nil )
        return plasmaLayer->MustBeUnique();

    return false;
}

hsBool hsMaterialConverter::IUVGenHasDynamicScale(plMaxNode* node, StdUVGen *uvGen)
{
    hsGuardBegin("hsMaterialConverter::IUVGenHasDynamicScale");

    Control *ctl = nil;

    hsControlConverter::Instance().GetControllerByName(uvGen, TSTR("U Tiling"), ctl);
    if( ctl && (ctl->NumKeys() > 1) )
        return true;
    hsControlConverter::Instance().GetControllerByName(uvGen, TSTR("V Tiling"), ctl);
    if( ctl && (ctl->NumKeys() > 1) )
        return true;

    return false;
    hsGuardEnd; 
}

void hsMaterialConverter::IScaleLayerOpacity(plLayer* hLay, float scale)
{
    hsGuardBegin("hsMaterialConverter::IScaleLayerOpacity");

    if( scale < 1.f )
    {
        if( !(hLay->GetBlendFlags() & ~(hsGMatState::kBlendAlpha | hsGMatState::kBlendAntiAlias)) )
        {
            hLay->SetBlendFlags(hLay->GetBlendFlags() | hsGMatState::kBlendAlpha);
        }

        float opac = hLay->GetOpacity();
        opac *= scale;
        hLay->SetOpacity(scale);
    }
    hsGuardEnd;
}

hsGMaterial *hsMaterialConverter::ICheckForProjectedTexture(plMaxNode *node)
{
    hsGuardBegin("hsMaterialConverter::ICheckForProjectedTexture");

    char *nodeName = node->GetName();
    Object *obj = node->EvalWorldState(fConverterUtils.GetTime(fInterface)).obj;
    LightObject *light = (LightObject*)obj->ConvertToType(fConverterUtils.GetTime(fInterface), 
            Class_ID(SPOT_LIGHT_CLASS_ID,0));
    if( !light )
        light = (LightObject*)obj->ConvertToType(fConverterUtils.GetTime(fInterface), 
            Class_ID(FSPOT_LIGHT_CLASS_ID,0));
    if( !light )
        light = (LightObject*)obj->ConvertToType(fConverterUtils.GetTime(fInterface), 
            Class_ID(OMNI_LIGHT_CLASS_ID,0));

    if( !light )
        light = (LightObject*)obj->ConvertToType(fConverterUtils.GetTime(fInterface), 
            Class_ID(DIR_LIGHT_CLASS_ID,0));

    if( !light )
        light = (LightObject*)obj->ConvertToType(fConverterUtils.GetTime(fInterface), 
            Class_ID(TDIR_LIGHT_CLASS_ID,0));

    if( light && light->GetProjector() )
    {
        Texmap *projMap;
        projMap = light->GetProjMap();
        return IWrapTextureInMaterial(projMap, node);
    }

    return nil;
    hsGuardEnd; 
}

hsGMaterial *hsMaterialConverter::IWrapTextureInMaterial(Texmap *texMap, plMaxNode *node)
{
    hsGuardBegin("hsMaterialConverter::IWrapTextureInMaterial");
    plLocation nodeLoc = node->GetLocation();
    //
    // Add material to list
    //
    int found=FALSE;
    char *nodeName = node->GetName();
    CStr className;
    texMap->GetClassName(className);

    // We want to keep it.  Handle appropriately.
    BitmapTex *bitmapTex = (BitmapTex *)texMap;
    plString txtFileName = plString::FromUtf8( bitmapTex->GetMapName() );

//  hsRegistryKey* key = hsgResMgr::ResMgr()->FindKey(txtFileName, hsGMaterial::Index());
    plKey key = node->FindPageKey( hsGMaterial::Index(), txtFileName );

    hsGMaterial *hMat = key ? hsGMaterial::ConvertNoRef(key->GetObjectPtr()) : nil;
    if( hMat )
    {
        CopyMaterialLODToTextures(hMat);
        return hMat;
    }

    hMat = new hsGMaterial;

    plLayer* hLay = new plLayer;
    hLay->InitToDefault();

    hsgResMgr::ResMgr()->NewKey(txtFileName, hLay,nodeLoc);

    if (texMap->ClassID() == hsMaxLayerClassID) 
    {
//      IProcessPlasmaLayer(hLay, nil, texMap, node, 0);
    }
    else 
    {
        //
        // Create hsGBitmap texture file from texture.
        // Add texture name to list if not already there.
        //
        
        // fill in benign values for our material
        hLay->SetAmbientColor(hsColorRGBA().Set(0.f, 0.f, 0.f, 1.f));
        hLay->SetRuntimeColor(hsColorRGBA().Set(1.f, 1.f, 1.f, 1.f));

        // If we've got No Preshading explicitly set, then set the 
        // PreshadeColor to black (so anything in the vertex color
        // will get ignored at runtime).
        if( node->GetRunTimeLight() )
            hLay->SetPreshadeColor(hsColorRGBA().Set(0.f, 0.f, 0.f, 1.f));
        else
            hLay->SetPreshadeColor(hsColorRGBA().Set(1.f, 1.f, 1.f, 1.f));
        hLay->SetOpacity(1.f);
        
        uint32_t autoStart = 0;
        char *nodeName = node->GetName();
        char *texName = bitmapTex->GetName();
        // BEGIN OVERRIDE
        if (ALPHA_NONE == bitmapTex->GetAlphaSource())
        {
            // TEMP
            // This should be a blend modifier, not a load flag - mf
            // hLay->SetLoadFlags(hLay->GetLoadFlags() | hsGLayer::kLoadNoAlpha);
        }
        else if (!(hLay->GetBlendFlags() & (hsGMatState::kBlendMask & ~hsGMatState::kBlendAntiAlias)))
        {
            if (ALPHA_FILE == bitmapTex->GetAlphaSource())
                hLay->SetBlendFlags(hLay->GetBlendFlags() | hsGMatState::kBlendAlpha);
        }
#if 0       
        // Texture map info
        if (texMap != NULL)
        {
            IInitAttrTexture(node, nil, hLay, texMap, nodeName);
        }
#endif
    }

    key = hsgResMgr::ResMgr()->NewKey(txtFileName, hMat,nodeLoc);
    
    IAddLayerToMaterial(hMat, hLay);

    CopyMaterialLODToTextures(hMat);

    return hMat;
    hsGuardEnd; 
}

Mtl* hsMaterialConverter::GetBaseMtl(Mtl* mtl)
{
    hsGuardBegin("hsMaterialConverter::GetBaseMtl");

    return (mtl);
    hsGuardEnd; 
}

Mtl* hsMaterialConverter::GetBaseMtl(plMaxNode* node)
{ 
    return GetBaseMtl(node->GetMtl()); 
}

enum plFunkyNess
{
    kFunkyNone          = 0x0,
    kFunkyDistance      = 0x1,
    kFunkyNormal        = 0x2,
    kFunkyReflect       = 0x4,
    kFunkyUp            = 0x8,
    kFunkyMask          = kFunkyDistance
                        | kFunkyNormal
                        | kFunkyReflect
                        | kFunkyUp,
    kFunkyAdd
};

static float IClampToRange(float v, float lo, float hi)
{
    if( v < lo )
        v = lo;
    else if( v > hi )
        v = hi;

    return v;
}

uint32_t hsMaterialConverter::IGetOpacityRanges(plMaxNode* node, Texmap* texMap, float& tr0, float& op0, float& op1, float& tr1)
{
    if( node->HasFade() )
    {
        Box3 fade = node->GetFade();
        tr0 = fade.Min()[0];
        op0 = fade.Min()[1];
        op1 = fade.Max()[1];
        tr1 = fade.Max()[0];

        uint32_t funkyType = kFunkyDistance;

        if( (tr0 == op0) && (op1 == tr1) )
        {
            funkyType = kFunkyNone;
        }
        // See if we're going opaque to transparent to opaque. Then we need the Additive LUT
        else
        if( (tr0 > op0) && (op1 > tr1) )
        {
            funkyType |= kFunkyAdd;
        }
        return funkyType;
    }


    if( !(texMap && texMap->GetName() && (strlen(texMap->GetName()) > 3)) )
        return kFunkyNone;

    const char* field = texMap->GetName()+3;
    float f0, f1, f2, f3;
    int code = sscanf(field, "%g,%g,%g,%g", &f0, &f1, &f2, &f3);
    if( !code || (code == EOF) )
    {
        return kFunkyNone;
    }
    switch( code )
    {
    case 1:
        tr1 = f0;
        tr0 = op0 = op1 = 0;
        break;
    case 2:
        op1 = f0;
        tr1 = f1;
        tr0 = op0 = 0;
        break;
    case 3:
        tr0 = 0;
        op0 = f0;
        op1 = f1;
        tr1 = f2;
        break;
    case 4:
        tr0 = f0;
        op0 = f1;
        op1 = f2;
        tr1 = f3;
        break;

    default:
        hsAssert(false, "Whoa, am i confused!!!");
        return kFunkyNone;
    }

    uint32_t funkyType = IGetFunkyType(texMap);
    switch( funkyType & kFunkyMask )
    {
    case kFunkyDistance:
        tr0 = IClampToRange(tr0, 0, 1.e33f);
        op0 = IClampToRange(op0, 0, 1.e33f);
        op1 = IClampToRange(op1, 0, 1.e33f);
        tr1 = IClampToRange(tr1, 0, 1.e33f);
        break;
    case kFunkyNormal:
    case kFunkyUp:
        tr0 = IClampToRange(tr0, 0, 180.f);
        op0 = IClampToRange(op0, 0, 180.f);
        op1 = IClampToRange(op1, 0, 180.f);
        tr1 = IClampToRange(tr1, 0, 180.f);
        break;
    case kFunkyReflect:
        tr0 = IClampToRange(tr0, 0, 180.f);
        op0 = IClampToRange(op0, 0, 180.f);
        op1 = IClampToRange(op1, 0, 180.f);
        tr1 = IClampToRange(tr1, 0, 180.f);
        break;
    }
    if( tr0 > tr1 )
    {
        float t;
        t = tr0;
        tr0 = tr1;
        tr1 = t;

        t = op0;
        op0 = op1;
        op1 = t;
    }
    // Check for degenerate ranges.
    if( (tr0 == op0) && (op1 == tr1) )
    {
        funkyType = kFunkyNone;
    }
    // See if we're going opaque to transparent to opaque. Then we need the Additive LUT
    else
    if( (tr0 > op0) && (op1 > tr1) )
    {
        funkyType |= kFunkyAdd;
    }
    switch( funkyType & kFunkyMask )
    {
    case kFunkyDistance:
        break;
    case kFunkyNormal:
    case kFunkyUp:
        tr0 = cos(hsDegreesToRadians(tr0));
        op0 = cos(hsDegreesToRadians(op0));
        op1 = cos(hsDegreesToRadians(op1));
        tr1 = cos(hsDegreesToRadians(tr1));
        break;
    case kFunkyReflect:
        tr0 = cos(hsDegreesToRadians(tr0));
        op0 = cos(hsDegreesToRadians(op0));
        op1 = cos(hsDegreesToRadians(op1));
        tr1 = cos(hsDegreesToRadians(tr1));
        break;
    }
    return funkyType;
}

uint32_t hsMaterialConverter::IGetFunkyType(Texmap* texMap)
{
    if( texMap && texMap->GetName() && *texMap->GetName() )
    {
        // Distance opacity
        if( !_strnicmp(texMap->GetName(), "%%%", 3) )
            return kFunkyDistance;
        // Angle opacity - normal
        if( !_strnicmp(texMap->GetName(), "@@@", 3) )
            return kFunkyNormal;
        // Angle opacity - reflection
        if( !_strnicmp(texMap->GetName(), "$$$", 3) )
            return kFunkyReflect;
        if( !_strnicmp(texMap->GetName(), "!!!", 3) )
            return kFunkyUp;
    }
    return kFunkyNone;
}

hsBool hsMaterialConverter::IHasFunkyOpacity(plMaxNode* node, Texmap* texMap)
{
    float tr0, cp0, cp1, tr1;
    return IGetOpacityRanges(node, texMap, tr0, cp0, cp1, tr1) != kFunkyNone;
}

void hsMaterialConverter::IAppendFunkyLayer(plMaxNode* node, Texmap* texMap, hsGMaterial* mat)
{
    plLayer* prevLay = plLayer::ConvertNoRef(mat->GetLayer(mat->GetNumLayers()-1)->BottomOfStack());
    hsAssert(prevLay, "Lost our base layer");
    if( prevLay )
        prevLay->SetMiscFlags(prevLay->GetMiscFlags() | hsGMatState::kMiscBindNext);

    float tr0, op0, op1, tr1;
    uint32_t funkyType = IGetOpacityRanges(node, texMap, tr0, op0, op1, tr1);

    if( funkyType == kFunkyNone )
        return;

    hsMatrix44 uvwXfm;
    uvwXfm.Reset();
    uvwXfm.fMap[0][0] = uvwXfm.fMap[1][1] = uvwXfm.fMap[2][2] = 0;
    uvwXfm.NotIdentity();

    if( op0 != tr0 )
    {
        uvwXfm.fMap[0][2] = -1.f / (tr0 - op0);
        uvwXfm.fMap[0][3] = uvwXfm.fMap[0][2] * -tr0;
    }
    else
    {
        uvwXfm.fMap[0][3] = 1.f;
    }

    if( op1 != tr1 )
    {
        uvwXfm.fMap[1][2] = -1.f / (tr1 - op1);
        uvwXfm.fMap[1][3] = uvwXfm.fMap[1][2] * -tr1;
    }
    else
    {
        uvwXfm.fMap[1][3] = 1.f;
    }

    plBitmap* funkRamp = IGetFunkyRamp(node, funkyType);

    plString name = plString::Format("%s_funkRamp", prevLay->GetKey()->GetName().c_str());

    plLayer* layer = new plLayer;
    layer->InitToDefault();
    hsgResMgr::ResMgr()->NewKey(name, layer, node->GetLocation());
    hsgResMgr::ResMgr()->AddViaNotify(funkRamp->GetKey(), new plLayRefMsg(layer->GetKey(), plRefMsg::kOnCreate, 0, plLayRefMsg::kTexture), plRefFlags::kActiveRef);

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

    layer->SetZFlags(hsGMatState::kZNoZWrite);
    uint32_t blendFlags = hsGMatState::kBlendAlpha | hsGMatState::kBlendNoTexColor | hsGMatState::kBlendAlphaMult;
    layer->SetBlendFlags(blendFlags);
    layer->SetClampFlags(hsGMatState::kClampTexture);

    layer->SetTransform(uvwXfm);

    switch( funkyType & kFunkyMask )
    {
    case kFunkyDistance:
        layer->SetUVWSrc(plLayerInterface::kUVWPosition);
        layer->SetMiscFlags(layer->GetMiscFlags() | hsGMatState::kMiscNoShadowAlpha);
        break;
    case kFunkyNormal:
        layer->SetUVWSrc(plLayerInterface::kUVWNormal);
        break;
    case kFunkyUp:
        layer->SetUVWSrc(plLayerInterface::kUVWNormal);
        layer->SetMiscFlags(layer->GetMiscFlags() | hsGMatState::kMiscOrthoProjection);
        break;
    case kFunkyReflect:
        layer->SetUVWSrc(plLayerInterface::kUVWReflect);
        break;
    }

    hsgResMgr::ResMgr()->AddViaNotify(layer->GetKey(), new plMatRefMsg(mat->GetKey(), plRefMsg::kOnCreate, 
                                                                       -1, plMatRefMsg::kLayer), plRefFlags::kActiveRef);

}

plBitmap* hsMaterialConverter::IGetFunkyRamp(plMaxNode* node, uint32_t funkyType)
{
    plString funkName = funkyType & kFunkyAdd ? _TEMP_CONVERT_FROM_LITERAL("FunkyRampAdd") : _TEMP_CONVERT_FROM_LITERAL("FunkyRampMult");

    const int kLUTWidth = 16;
    const int kLUTHeight = 16;

    // NOTE: CreateBlankMipmap might return an old mipmap if it was already created, so in those
    // cases we wouldn't really need to re-write the texture. However, there's no harm in doing so,
    // and since we're close to Alpha, I don't want to shake up the code any more than absolutely 
    // necessary. -mcn
    plMipmap *texture = plBitmapCreator::Instance().CreateBlankMipmap( kLUTWidth, kLUTHeight, plMipmap::kARGB32Config, 1, funkName, node->GetLocation() );

    uint32_t* pix = (uint32_t*)texture->GetImage();

    if( funkyType & kFunkyAdd )
    {
        int i;
        for( i = 0; i < kLUTHeight; i++ )
        {
            int j;
            for( j = 0; j < kLUTWidth; j++ )
            {
                float x = float(j) / (kLUTWidth-1);
                float y = float(i) / (kLUTHeight-1);
                if( x < y )
                    x = y;
                *pix++ = MakeUInt32Color(1.f, 1.f, 1.f, x);
            }
        }
    }
    else
    {
        int i;
        for( i = 0; i < kLUTHeight; i++ )
        {
            int j;
            for( j = 0; j < kLUTWidth; j++ )
            {
                float x = float(j) / (kLUTWidth-1);
                float y = float(i) / (kLUTHeight-1);
                *pix++ = MakeUInt32Color(1.f, 1.f, 1.f, x*y);
            }
        }
    }

    return texture;
}

void hsMaterialConverter::IAppendWetLayer(plMaxNode* node, hsGMaterial* mat)
{
    // Find the "wet" environment map
    int i;
    for( i = 0; i < 24; i++ )
    {
        MtlBase* mtl = fInterface->GetMtlSlot(i);
        if( mtl && (mtl->GetName() == TSTR("Wet(*)")) )
        {
            if( mtl->SuperClassID() != MATERIAL_CLASS_ID )
                continue;

            hsTArray<hsGMaterial*> matList;
            if( !GetMaterialArray((Mtl*)mtl, node, matList, 0) )
                return; // oh well, thanks for playing...

            // Okay, got one (at least one, hopefully just one, but this is trash code anyway, right?
            plLayerInterface* envLay = matList[0]->GetLayer(0);

            // Append it to the material
            hsgResMgr::ResMgr()->AddViaNotify(envLay->GetKey(), 
                    new plMatRefMsg(mat->GetKey(), plRefMsg::kOnCreate, -1, plMatRefMsg::kLayer), plRefFlags::kActiveRef);

            break;
        }
    }
    // Badly set up here
    if( i == 24 )
        return;

    // Now throw on the FunkyUp map. Wait here while I pull some parameters out of my

    plLayer* prevLay = plLayer::ConvertNoRef(mat->GetLayer(mat->GetNumLayers()-1)->BottomOfStack());
    hsAssert(prevLay, "Lost our base layer");
    if( prevLay )
        prevLay->SetMiscFlags(prevLay->GetMiscFlags() | hsGMatState::kMiscBindNext | hsGMatState::kMiscRestartPassHere);

    hsMatrix44 uvwXfm;
    uvwXfm.Reset();
    uvwXfm.fMap[0][0] = uvwXfm.fMap[1][1] = uvwXfm.fMap[2][2] = 0;
    uvwXfm.NotIdentity();

    uvwXfm.fMap[0][3] = 1.f;

    float op = 0.8f;
    float tr = 0.5f;
    uvwXfm.fMap[1][2] = -1.f / (tr - op);
    uvwXfm.fMap[1][3] = uvwXfm.fMap[1][2] * -tr;

    plString name = plString::Format("%s_funkRamp", prevLay->GetKey()->GetName().c_str());

    plLayer* layer = nil;
    plKey key = node->FindPageKey( plLayer::Index(), name );
    if( key )
        layer = plLayer::ConvertNoRef(key->GetObjectPtr());
    if( !layer )
    {
        layer = new plLayer;
        layer->InitToDefault();

        hsgResMgr::ResMgr()->NewKey(name, layer, node->GetLocation());

        plBitmap* funkRamp = IGetFunkyRamp(node, kFunkyUp);
        hsgResMgr::ResMgr()->AddViaNotify(funkRamp->GetKey(), new plLayRefMsg(layer->GetKey(), plRefMsg::kOnCreate, 0, plLayRefMsg::kTexture), plRefFlags::kActiveRef);
    }

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

    layer->SetZFlags(hsGMatState::kZNoZWrite);
    uint32_t blendFlags = hsGMatState::kBlendAlpha | hsGMatState::kBlendNoTexColor | hsGMatState::kBlendAlphaMult;
    layer->SetBlendFlags(blendFlags);
    layer->SetClampFlags(hsGMatState::kClampTexture);

    layer->SetTransform(uvwXfm);

    layer->SetUVWSrc(plLayerInterface::kUVWNormal);
    layer->SetMiscFlags(layer->GetMiscFlags() | hsGMatState::kMiscOrthoProjection);

    hsgResMgr::ResMgr()->AddViaNotify(layer->GetKey(), new plMatRefMsg(mat->GetKey(), plRefMsg::kOnCreate, 
                                                                       -1, plMatRefMsg::kLayer), plRefFlags::kActiveRef);

}

hsBool hsMaterialConverter::HasVisDists(plMaxNode* node, int iSubMtl, float& minDist, float& maxDist)
{
    const char* dbgNodeName = node->GetName();
    const char* dbgMatName = node->GetMtl() ? node->GetMtl()->GetName() : "Dunno";

    if( node->HasFade() )
    {
        const float kMaxMaxDist = 1.e10f;
        Box3 fade = node->GetFade();
        minDist = maxDist = 0;
        if( fade.Min()[2] < 0 )
        {
            minDist = fade.Min()[0];
            maxDist = kMaxMaxDist;
        }

        if( fade.Max()[2] > 0 )
            maxDist = fade.Max()[0];

        return maxDist > minDist;
    }

    Mtl* mtl = node->GetMtl();
    if( IsMultiMat(mtl) )
    {
        if( iSubMtl >= mtl->NumSubMtls() )
            iSubMtl = 0;
        mtl = mtl->GetSubMtl(iSubMtl);
    }

    return HasVisDists(node, mtl, minDist, maxDist);
}

hsBool hsMaterialConverter::HasVisDists(plMaxNode* node, Mtl* mtl, float& minDist, float& maxDist)
{
    const char* dbgNodeName = node->GetName();
    const char* dbgMatName = node->GetMtl() ? node->GetMtl()->GetName() : "Dunno";

    if( node->HasFade() )
    {
        Box3 fade = node->GetFade();
        if( fade.Min()[2] < 0 )
            minDist = fade.Min()[0];
        else
            minDist = 0;

        if( fade.Max()[2] > 0 )
            maxDist = fade.Max()[0];
        else
            maxDist = 0;
        return maxDist > minDist;
    }

    minDist = -1.f;
    maxDist = 1.e33f;

    if( IsHsMaxMat(mtl) )
    {
        hsBool baseFunky = false;
        hsBool topFunky = true;
        plPassMtl* passMtl = (plPassMtl*)mtl;
        float tr0, op0, op1, tr1;
        uint32_t funkyType = IGetOpacityRanges(node, mtl->GetSubTexmap(0), tr0, op0, op1, tr1);

        if( kFunkyDistance == (funkyType & kFunkyMask) )
        {
            if( tr0 < op0 )
            {
                minDist = tr0;
                baseFunky = true;
            }
            if( tr1 > op1 )
            {
                maxDist = tr1;
                baseFunky = true;
            }
        }

        if( passMtl->GetTopLayerOn() )
        {
            topFunky = false;
            funkyType = IGetOpacityRanges(node, mtl->GetSubTexmap(1), tr0, op0, op1, tr1);
            if( kFunkyDistance == (funkyType & kFunkyMask) )
            {
                if( tr0 < op0 )
                {
                    if( minDist > tr0 )
                        minDist = tr0;
                    topFunky = true;
                }
                if( tr1 > op1 )
                {
                    if( maxDist < tr1 )
                        maxDist = tr1;
                    topFunky = true;
                }
            }
        }
        return baseFunky && topFunky;
    }

    if( IsMultipassMat(mtl) )
    {
        minDist = 1.e33f;
        maxDist = -1.f;
        int i;
        for( i = 0; i < mtl->NumSubMtls(); i++ )
        {
            float minD, maxD;
            if( !HasVisDists(node, mtl->GetSubMtl(i), minD, maxD) )
                return false;

            if( minDist > minD )
                minDist = minD;
            if( maxDist < maxD )
                maxDist = maxD;
        }
        return true;
    }

    return false;
}


hsBool hsMaterialConverter::IsBumpLayer(Texmap* texMap)
{
    if( texMap 
        && (texMap->ClassID() == LAYER_TEX_CLASS_ID) 
        && !strncmp(texMap->GetName(), kSecretBumpSign, strlen(kSecretBumpSign)) )
    {
        return true;
    }
    return false;
}

hsBool hsMaterialConverter::IsBumpMtl(Mtl* mtl)
{
    if( mtl == nil )
        return false;       // Well, gee, I guess it can't be a bumpmap then...

    const char* dbgMtlName = mtl->GetName();

    if( mtl->ClassID() == BUMP_MTL_CLASS_ID )
        return true;

    return false;
}

hsBool hsMaterialConverter::HasBumpLayer(plMaxNode* node, Mtl* mtl)
{
    if( !mtl )
        return false;

    // Multi-sub or a multi-pass, recurse on down
    if( IsMultiMat(mtl) || IsMultipassMat(mtl) )
    {
        int i;
        for( i = 0; i < mtl->NumSubMtls(); i++ )
        {
            if( HasBumpLayer(node, mtl->GetSubMtl(i)) )
                return true;
        }
        return false;
    }

    // Composite, screw it, just say no!
    if( IsCompositeMat(mtl) )
        return false;

    // Particle? Give me a break.
    if( IsParticleMat(mtl) )
        return false;

#ifdef BUMP_EXCLUDE_DECAL
    // Decal? Maybe someday, but not today.
    if( IsDecalMat(mtl) )
        return false;
#endif // BUMP_EXCLUDE_DECAL

    // A closer look at the material.
    if( IsBumpMtl(mtl) )
        return true;

    if( IsHsMaxMat(mtl) || IsDecalMat(mtl) )
    {
        plPassMtlBase   *passMtl = (plPassMtlBase *)mtl;
#ifdef BUMP_EXCLUDE_MULTILAYER
        if( passMtl->GetTopLayerOn() )
            return false;
#else // BUMP_EXCLUDE_MULTILAYER
        if( passMtl->GetTopLayerOn() && IsBumpLayer(mtl->GetSubTexmap(1)) )
            return true;
#endif // BUMP_EXCLUDE_MULTILAYER
        
        if( IsBumpLayer(mtl->GetSubTexmap(0)) )
            return true;
    }
    return false;
}

BitmapTex* hsMaterialConverter::GetBumpLayer(plMaxNode* node, Mtl* mtl)
{
    hsAssert(!IsMultiMat(mtl), "Material passed in here should be per face");

    if( IsMultipassMat(mtl) )
    {
        int i;
        for( i = 0; i < mtl->NumSubMtls(); i++ )
        {
            BitmapTex* retVal = GetBumpLayer(node, mtl->GetSubMtl(i));
            if( retVal )
                return retVal;
        }
        return nil;
    }
    if( IsBumpMtl(mtl) )
    {
        Texmap* texMap = mtl->GetSubTexmap(0);

        if( texMap && (texMap->ClassID() == LAYER_TEX_CLASS_ID) )
        {
            return (BitmapTex*)texMap;
        }
        return nil;
    }
    if( HasBumpLayer(node, mtl) )
    {
        Texmap* texMap = nil;

#ifndef BUMP_EXCLUDE_MULTILAYER
        // Safe cast, because only PassMtl (and derivatives) are BumpMtls.
        plPassMtl* passMtl = (plPassMtl*)mtl;

        if( passMtl->GetTopLayerOn() && IsBumpLayer(mtl->GetSubTexmap(1)) )
            texMap = mtl->GetSubTexmap(1);
#endif // BUMP_EXCLUDE_MULTILAYER

        if( !texMap )
            texMap = mtl->GetSubTexmap(0);

        if( texMap && (texMap->ClassID() == LAYER_TEX_CLASS_ID) )
        {
            return (BitmapTex*)texMap;
        }
    }
    return nil;
}

plMipmap *hsMaterialConverter::IGetBumpLutTexture(plMaxNode *node)
{
    const plString texName = _TEMP_CONVERT_FROM_LITERAL("BumpLutTexture");

//#define FUNKYBUMP
#ifndef FUNKYBUMP
    const int kLUTWidth = 16;
    const int kLUTHeight = 16;

    // NOTE: CreateBlankMipmap might return an old mipmap if it was already created, so in those
    // cases we wouldn't really need to re-write the texture. However, there's no harm in doing so,
    // and since we're close to Alpha, I don't want to shake up the code any more than absolutely 
    // necessary. -mcn
    plMipmap *texture = plBitmapCreator::Instance().CreateBlankMipmap( kLUTWidth, kLUTHeight, plMipmap::kARGB32Config, 1, texName, node->GetLocation() );

    // THIS IS UNTESTED FOR ANY HEIGHT OTHER THAN 16. BEWARE THE DREAD OFF-BY-ONE!!!
    int delH = (kLUTHeight-1) / 5;
    int startH = delH / 2 + 1;
    int doneH = 0;

    // set the color data
    uint32_t* pix = (uint32_t*)texture->GetImage();
    int i;

    // Red ramps, one with G,B = 0,0, one with G,B = 127,127
    for( i = 0; i < startH; i++ )
    {
        int j;
        for( j = 0; j < kLUTWidth; j++ )
        {
            float x = float(j) / (kLUTWidth-1);
            *pix++ = MakeUInt32Color(x, 0, 0, 1.f);
        }
    }
    doneH = i;
    for( i = i; i < doneH + delH; i++ )
    {
        int j;
        for( j = 0; j < kLUTWidth; j++ )
        {
            float x = float(j) / (kLUTWidth-1);
            *pix++ = MakeUInt32Color(x, 0.5f, 0.5f, 1.f);
        }
    }
    doneH = i;

    // Green ramps, one with R,B = 0,0, one with R,B = 127,127
    for( i = i; i < doneH + delH; i++ )
    {
        int j;
        for( j = 0; j < kLUTWidth; j++ )
        {
            float x = float(j) / (kLUTWidth-1);
            *pix++ = MakeUInt32Color(0, x, 0, 1.f);
        }
    }
    doneH = i;
    for( i = i; i < doneH + delH; i++ )
    {
        int j;
        for( j = 0; j < kLUTWidth; j++ )
        {
            float x = float(j) / (kLUTWidth-1);
            *pix++ = MakeUInt32Color(0.5f, x, 0.5f, 1.f);
        }
    }
    doneH = i;

    // Blue ramps, one with R,G = 0,0, one with R,G = 127,127
    for( i = i; i < doneH + delH; i++ )
    {
        int j;
        for( j = 0; j < kLUTWidth; j++ )
        {
            float x = float(j) / (kLUTWidth-1);
            *pix++ = MakeUInt32Color(0, 0, x, 1.f);
        }
    }
    for( i = i; i < kLUTHeight; i++ )
    {
        int j;
        for( j = 0; j < kLUTWidth; j++ )
        {
            float x = float(j) / (kLUTWidth-1);
//              *pix++ = MakeUInt32Color(0.25f, 0.25f, x, 1.f);
            *pix++ = MakeUInt32Color(0.5f, 0.5f, x, 1.f);
        }
    }
#else // FUNKYBUMP
    const int kLUTWidth = 16;
    const int kLUTHeight = 16;

    // NOTE: CreateBlankMipmap might return an old mipmap if it was already created, so in those
    // cases we wouldn't really need to re-write the texture. However, there's no harm in doing so,
    // and since we're close to Alpha, I don't want to shake up the code any more than absolutely 
    // necessary. -mcn
    plMipmap *texture = plBitmapCreator::CreateBlankMipmap( kLUTWidth, kLUTHeight, plMipmap::kARGB32Config, 1, texName, node->GetLocation() );

    // THIS IS UNTESTED FOR ANY HEIGHT OTHER THAN 16. BEWARE THE DREAD OFF-BY-ONE!!!
    int delH = (kLUTHeight-1) / 5;
    int startH = delH / 2 + 1;
    int doneH = 0;

    // set the color data
    uint32_t* pix = (uint32_t*)texture->GetImage();
    int i;

    const float kWScale = 1.f;
    // Red ramps, one with G,B = 0,0, one with G,B = 127,127
    for( i = 0; i < startH; i++ )
    {
        int j;
        for( j = 0; j < kLUTWidth; j++ )
        {
            float x = float(j) / (kLUTWidth-1);
            *pix++ = MakeUInt32Color(x, 0, 0, 1.f);
        }
    }
    doneH = i;
    for( i = i; i < doneH + delH; i++ )
    {
        int j;
        for( j = 0; j < kLUTWidth; j++ )
        {
            float x = float(j) / (kLUTWidth-1);
            if( x > 0.5f )
                *pix++ = MakeUInt32Color(x, 0.5f, (x-0.5f) * kWScale + 0.5f, 1.f);
            else
                *pix++ = MakeUInt32Color(x, 0.5f, (1.f - x - 0.5f) * kWScale + 0.5f, 1.f);
        }
    }
    doneH = i;

    // Green ramps, one with R,B = 0,0, one with R,B = 127,127
    for( i = i; i < doneH + delH; i++ )
    {
        int j;
        for( j = 0; j < kLUTWidth; j++ )
        {
            float x = float(j) / (kLUTWidth-1);
            *pix++ = MakeUInt32Color(0, x, 0, 1.f);
        }
    }
    doneH = i;
    for( i = i; i < doneH + delH; i++ )
    {
        int j;
        for( j = 0; j < kLUTWidth; j++ )
        {
            float x = float(j) / (kLUTWidth-1);
            if( x > 0.5f )
                *pix++ = MakeUInt32Color(0.5f, x, (x-0.5f) * kWScale + 0.5f, 1.f);
            else
                *pix++ = MakeUInt32Color(0.5f, x, (1.f - x - 0.5f) * kWScale + 0.5f, 1.f);
        }
    }
    doneH = i;

    // Blue ramps, one with R,G = 0,0, one with R,G = 127,127
    for( i = i; i < doneH + delH; i++ )
    {
        int j;
        for( j = 0; j < kLUTWidth; j++ )
        {
            float x = float(j) / (kLUTWidth-1);
            *pix++ = MakeUInt32Color(0, 0, x, 1.f);
        }
    }
    for( i = i; i < kLUTHeight; i++ )
    {
        int j;
        for( j = 0; j < kLUTWidth; j++ )
        {
            float x = float(j) / (kLUTWidth-1);
            *pix++ = MakeUInt32Color(0.25f, 0.25f, x, 1.f);
        }
    }
#endif // FUNKYBUMP

    return texture;
}

plLayer* hsMaterialConverter::IMakeBumpLayer(plMaxNode* node, const plString& nameBase, hsGMaterial* mat, uint32_t miscFlag)
{
    plString name;
    switch( miscFlag & hsGMatState::kMiscBumpChans )
    {
    case hsGMatState::kMiscBumpDu:
        name = plString::Format("%s_DU_BumpLut", nameBase.c_str());
        break;
    case hsGMatState::kMiscBumpDv:
        name = plString::Format("%s_DV_BumpLut", nameBase.c_str());
        break;
    case hsGMatState::kMiscBumpDw:
        name = plString::Format("%s_DW_BumpLut", nameBase.c_str());
        break;
    default:
        hsAssert(false, "Bogus flag input to MakeBumpLayer");
        return nil;
    }

    plMipmap* bumpLutTexture = IGetBumpLutTexture(node);

    plLayer* layer = new plLayer;
    layer->InitToDefault();
    hsgResMgr::ResMgr()->NewKey(name, layer, node->GetLocation());
    hsgResMgr::ResMgr()->AddViaNotify(bumpLutTexture->GetKey(), new plLayRefMsg(layer->GetKey(), plRefMsg::kOnCreate, 0, plLayRefMsg::kTexture), plRefFlags::kActiveRef);

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

    layer->SetZFlags(hsGMatState::kZNoZWrite);
    uint32_t blendFlags = miscFlag & hsGMatState::kMiscBumpDu ? hsGMatState::kBlendMADD : hsGMatState::kBlendAdd;
    layer->SetBlendFlags(blendFlags);
    layer->SetClampFlags(hsGMatState::kClampTexture);
    layer->SetMiscFlags(miscFlag);
    layer->SetMiscFlags(layer->GetMiscFlags() | hsGMatState::kMiscBindNext);

    if( miscFlag & hsGMatState::kMiscBumpDu )
        layer->SetMiscFlags(layer->GetMiscFlags() | hsGMatState::kMiscRestartPassHere);

    int i;
    int uvChan = -1;

    // Find our UVW channel. If there's another layer wanting to use the same kind of
    // channel, just grab that. Otherwise we need to reserve one ourselves.
    for( i = 0; i < mat->GetNumLayers(); i++ )
    {
        if( (mat->GetLayer(i)->GetMiscFlags() & hsGMatState::kMiscBumpChans) == (miscFlag & hsGMatState::kMiscBumpChans) )
            uvChan = mat->GetLayer(i)->GetUVWSrc();
    }

    if( uvChan < 0 )
    {
        uvChan = 0;
        for( i = 0; i < mat->GetNumLayers(); i++ )
        {
            if( uvChan <= int(mat->GetLayer(i)->GetUVWSrc() & plLayerInterface::kUVWIdxMask) )
                uvChan = int(mat->GetLayer(i)->GetUVWSrc() & plLayerInterface::kUVWIdxMask) + 1;
        }
        // Lightmap layers haven't been created and inserted yet, but they have reserved their
        // UVW channel. Just don't take it.
        if( node->IsLegalDecal() )
            node = (plMaxNode*)node->GetParentNode();
        if( node->GetLightMapComponent() )
        {
            if( uvChan == node->GetLightMapComponent()->GetUVWSrc() )
                uvChan++;
        }
        if( miscFlag & hsGMatState::kMiscBumpDv )
            uvChan++;
        if( miscFlag & hsGMatState::kMiscBumpDw )
            uvChan |= plLayerInterface::kUVWNormal;
    }

    layer->SetUVWSrc(uvChan);

    return layer;
}

void hsMaterialConverter::IInsertBumpLayers(plMaxNode* node, hsGMaterial* mat, int bumpLayerIdx)
{
    // First let's monkey with the incoming layer, just to confuse things.
    // This doesn't actually do anything useful, it's just to spite anyone trying to read this code.
    hsBool isAdd = 0 != (mat->GetLayer(bumpLayerIdx)->GetBlendFlags() & hsGMatState::kBlendAdd);
    plLayer* bumpLay = plLayer::ConvertNoRef(mat->GetLayer(bumpLayerIdx)->BottomOfStack());
    if( bumpLay )
        bumpLay->SetBlendFlags(
                        (bumpLay->GetBlendFlags() & ~hsGMatState::kBlendMask)
                        | hsGMatState::kBlendDot3);

    plString name = mat->GetLayer(bumpLayerIdx)->GetKey()->GetName();

    plLayer* layerDu = IMakeBumpLayer(node, name, mat, hsGMatState::kMiscBumpDu);
    plLayer* layerDv = IMakeBumpLayer(node, name, mat, hsGMatState::kMiscBumpDv);
    plLayer* layerDw = IMakeBumpLayer(node, name, mat, hsGMatState::kMiscBumpDw);

    if( isAdd )
        layerDu->SetBlendFlags((layerDu->GetBlendFlags() & ~hsGMatState::kBlendMask) | hsGMatState::kBlendAdd);

    // Insert it in the right spot.
    hsgResMgr::ResMgr()->AddViaNotify(layerDv->GetKey(), new plMatRefMsg(mat->GetKey(), plRefMsg::kOnCreate, 
                                                                       bumpLayerIdx, plMatRefMsg::kLayer | plMatRefMsg::kInsert), plRefFlags::kActiveRef);
    hsgResMgr::ResMgr()->AddViaNotify(layerDw->GetKey(), new plMatRefMsg(mat->GetKey(), plRefMsg::kOnCreate, 
                                                                       bumpLayerIdx, plMatRefMsg::kLayer | plMatRefMsg::kInsert), plRefFlags::kActiveRef);
    hsgResMgr::ResMgr()->AddViaNotify(layerDu->GetKey(), new plMatRefMsg(mat->GetKey(), plRefMsg::kOnCreate, 
                                                                       bumpLayerIdx, plMatRefMsg::kLayer | plMatRefMsg::kInsert), plRefFlags::kActiveRef);
}

void hsMaterialConverter::IInsertBumpLayers(plMaxNode* node, hsGMaterial* mat)
{
    int bumpLayerIdx = -1;

    // Look for a bump layer. If there aren't any, there's nothing to do, just return.
    // Currently only look for the first (and hopefully only) bump layer. 
    // BUMPTODO - Either correctly handle multiple bump layers (stupid) or detect and
    // slap the production wrist for trying it.
    // Okay, decided there actually are times it kind of makes sense to have multiple bump
    // layers (hint: what's wet and wavy).
    int i;
    for( i = 0; i < mat->GetNumLayers(); i++ )
    {
        if( mat->GetLayer(i)->GetMiscFlags() & hsGMatState::kMiscBumpLayer )
        {
            IInsertBumpLayers(node, mat, i);
            // Just inserted 3 layers before this one, adjust "i" to still be
            // pointed at this layer.
            i += 3;
        }
    }

}


//
//
//
Texmap *hsMaterialConverter::GetUVChannelBase(plMaxNode *node, Mtl* mtl, int which)
{
    hsGuardBegin("hsMaterialConverter::GetUVChannelBase");

    int i;

    // Forcing no flatten to support multiple uv channels
    return nil;

    // Forcing no flatten until flattening with MultiMat resolved.
    if( !GetBaseMtl(node->GetMtl())) // || MatWrite::IsMultiMat(node->GetMtl()) )
        return nil;

    if ( ForceNoUvsFlatten(node) )
        return nil;

    if ( !node )
        return nil;

    if ( !mtl )
        return nil;

    for (i = 0; i < mtl->NumSubTexmaps(); i++) {
        if (!((StdMat *)mtl)->MapEnabled(i))
            continue;

        Texmap *texMap = mtl->GetSubTexmap(i);

        if (!texMap)
            continue;
        
        if ( !(texMap->Requirements(-1) & MTLREQ_UV) &&
            !(texMap->Requirements(-1) & MTLREQ_UV2) )
            continue;

        if (which==0) {
            if (texMap->GetUVWSource() != UVWSRC_EXPLICIT)
                continue;
        } else {
            if (texMap->GetUVWSource() != UVWSRC_EXPLICIT2)
                continue;
        }
        
        if (ITextureTransformIsAnimated(texMap))
            continue;
        
        return (texMap);
    }

    return (0);
    hsGuardEnd; 
}

hsBool hsMaterialConverter::ClearDoneMaterials(plMaxNode* node)
{
    hsGuardBegin("hsMaterialConverter::ClearDoneMaterials");

    Mtl *mtl = GetBaseMtl(node);
    int isMultiMat = IsMultiMat(mtl);
    
    if (isMultiMat)
    {
        hsBool retVal = false;
        int32_t i;
        for (i = 0; i < mtl->NumSubMtls(); i++)
        {
            retVal = retVal || IClearDoneMaterial(mtl->GetSubMtl(i), node);
        }

        return (retVal);
    }
    else
    {
        return (IClearDoneMaterial(mtl, node));
    }

    hsGuardEnd; 
}

hsBool hsMaterialConverter::IClearDoneMaterial(Mtl* mtl, plMaxNode* node)
{
    hsGuardBegin("hsMaterialConverter::IClearDoneMaterial");

    hsBool doneSomething = false;
    if (fLastMaterial.fMaxMaterial == mtl)
    {
        fLastMaterial.fHsMaterial = nil;
        fLastMaterial.fMaxMaterial = nil;
        fLastMaterial.fMaxMaterial = nil;
        fLastMaterial.fSubMultiMat = false;
        fLastMaterial.fOwnedCopy = false;
    }

    int32_t i;
    for (i = fDoneMaterials.Count() - 1; i >= 0; --i)
    {
        if (fDoneMaterials[i].fMaxMaterial == mtl)
        {
            fDoneMaterials[i].fHsMaterial->GetKey()->UnRefObject();
            fDoneMaterials.Remove(i);
            doneSomething = true;
        }
    }

    return (doneSomething);
    hsGuardEnd; 
}

#define VIEW_UP 0
#define VIEW_DN 1
#define VIEW_LF 2
#define VIEW_RT 3
#define VIEW_FR 4
#define VIEW_BK 5


static BMM_Color_64 green64 = BMMCOLOR(0, (1<<16)-1, 0, (1<<16)-1)

BMM_Color_64 hsMaterialConverter::ICubeSample(Bitmap *bitmap[6], double phi, double theta) 
{
    hsGuardBegin("hsMaterialConverter::ICubeSample");

    theta = fmod(theta, (double)TWOPI);
    if( theta < 0 )theta += TWOPI;
    if( phi < 0 )phi = 0;
    else if( phi > PI )phi = PI;

    Bitmap *map = nil;

    double sinPhi = sin(phi);
    double cosPhi = cos(phi);
    double sinThe = sin(theta);
    double cosThe = cos(theta);
    const double sqrt2 = sqrt(2.0);
    const double oo_sqrt2 = 1.0 / sqrt2;
    const double oo_sqrt3 = 1.0 / sqrt(3.0);
    double sinPhiSinThe = sinPhi * sinThe;
    double sinPhiCosThe = sinPhi * cosThe;

    double x, y, z;
    double xMap, yMap;

    x = sinPhiSinThe;
    y = sinPhiCosThe;
    z = cosPhi;
    if( (z*z > x*x)&&(z*z > y*y) )
    {
        if( z > 0 )
        {
            map = bitmap[VIEW_UP];
            xMap = -x / z;
            yMap = -y / z;
        }
        else
        {
            map = bitmap[VIEW_DN];
            xMap = x / z;
            yMap = -y / z;
        }
    }
    else
    {
        if( (theta <= (M_PI / 2.0 - M_PI/4.0))
            ||(theta >= (M_PI * 2.0 - M_PI/4.0)) )
        {
            map = bitmap[VIEW_FR];
            xMap = x / y;
            yMap = -z / y;
        }
        else
        if( theta <= (M_PI - M_PI/4.0) )
        {
            map = bitmap[VIEW_LF];
            xMap = -y / x;
            yMap = -z / x;
        }
        else
        if( theta <= (M_PI * 3.0/2.0 - M_PI/4.0) )
        {
            map = bitmap[VIEW_BK];
            xMap = x / y;
            yMap = z / y;
        }
        else
        {
            map = bitmap[VIEW_RT];
            xMap = -y / x;
            yMap = z / x;
        }
    }
    xMap += 1.0;
    yMap += 1.0;
    xMap *= 0.5;
    yMap *= 0.5;
    int iMap, jMap;
    iMap = (int)(xMap * (map->Width()-1));
    jMap = (int)(yMap * (map->Height()-1));

    fErrorMsg->Set(!map, "CubeSample", "Bad fallthrough in spherefromcube").Check();
    BMM_Color_64 c;
    map->GetLinearPixels(iMap,jMap,1,&c);
    return c;

    hsGuardEnd; 
}

void hsMaterialConverter::IBuildSphereMap(Bitmap *bitmap[6], Bitmap *bm) 
{
    hsGuardBegin("hsMaterialConverter::IBuildSphereMap");

    int i, j;
    double delPhi = PI / bm->Height();
    double delThe = TWOPI / bm->Width();
    PixelBuf l64(bm->Width());
    BMM_Color_64 *pb=l64.Ptr();
    for( j = 0; j < bm->Height(); j++ )
    {
        for( i = 0; i < bm->Width(); i++ )
        {
            double phi, theta; // phi is up/down

            phi = (0.5 + j) * delPhi;
            theta = PI - (0.5 + i) * delThe;

            pb[i] = ICubeSample(bitmap, phi, theta);
        }
        bm->PutPixels(0,j, bm->Width(), pb);
    }

    hsGuardEnd;
}

hsBool hsMaterialConverter::ITextureTransformIsAnimated(Texmap *texmap)
{
    hsGuardBegin("hsMaterialConverter::IProcessAnimMaterial");

    if( !texmap )
        return false;

#if 0
    StdUVGen *uvGen = ((BitmapTex *)texmap)->GetUVGen();
    if( IsAnimatedByName(uvGen, TSTR("U Offset")) )
        return true;
    if( IsAnimatedByName(uvGen, TSTR("V Offset")) )
        return true;
    if( IsAnimatedByName(uvGen, TSTR("U Tiling")) )
        return true;
    if( IsAnimatedByName(uvGen, TSTR("V Tiling")) )
        return true;
    if( IsAnimatedByName(uvGen, TSTR("Angle")) )
        return true;

    if( IsAnimatedByName(uvGen, TSTR("U Angle")) )
        return true;
    if( IsAnimatedByName(uvGen, TSTR("V Angle")) )
        return true;
    if( IsAnimatedByName(uvGen, TSTR("W Angle")) )
        return true;

    return false;
#else
    CStr className;
    texmap->GetClassName(className);
    if( strcmp(className,"Bitmap") && strcmp(className,"Plasma Layer") && strcmp(className,"Plasma Layer Dbg."))
        return false;
    return (IHasAnimatedControllers(((BitmapTex *)texmap)->GetUVGen()));
#endif
    hsGuardEnd; 
}

hsBool hsMaterialConverter::IHasAnimatedControllers(Animatable* anim)
{
    hsGuardBegin("hsMaterialConverter::IHasAnimatedControllers");

    if( anim )
    {
        Control* ctl = GetControlInterface(anim);
        if (hsControlConverter::Instance().HasKeyTimes(ctl))
            return true;
        
        int nSub = anim->NumSubs();
        int i;
        for (i = 0; i < nSub; i++)
        {
            if (anim->SubAnim(i)==nil)
                continue;

            if( IHasAnimatedControllers(anim->SubAnim(i)) )
                return true;
        }
    }

    return false;
    hsGuardEnd; 
}


hsBool hsMaterialConverter::IIsAnimatedTexmap(Texmap* texmap)
{
    hsGuardBegin("hsMaterialConverter::IIsAnimatedTexmap");

    if (!texmap)
        return false;

    Control *ctl=nil;
    if (hsControlConverter::Instance().GetControllerByName(texmap, TSTR("Ambient"), ctl)) 
        return true;
    if (hsControlConverter::Instance().GetControllerByName(texmap, TSTR("Diffuse"), ctl)) 
        return true;
    if (hsControlConverter::Instance().GetControllerByName(texmap, TSTR("Color"), ctl)) 
        return true;
    if (hsControlConverter::Instance().GetControllerByName(texmap, TSTR("Opacity"), ctl)) 
        return true;

    if (HasAnimatedTextures(texmap) || IsAVILayer(texmap) || IsBinkLayer(texmap)|| IsQTLayer(texmap) || ITextureTransformIsAnimated(texmap))
        return true;

    return false;
    hsGuardEnd; 
}

//
// returns true if this material is animated
//
hsBool hsMaterialConverter::IsAnimatedMaterial(Mtl* mtl)
{
    hsGuardBegin("hsMaterialConverter::IsAnimatedMaterial");

    if (!mtl)
        return false;

    if (IsMultiMat(mtl))
    {
        int iMtl;
        for (iMtl = 0; iMtl < mtl->NumSubMtls(); iMtl++)
        {
            if (IsAnimatedMaterial(mtl->GetSubMtl(iMtl)))
                return true;
        }
        return false;
    }
    else
//  if (IsStdMat(mtl) || mtl->ClassID() == Class_ID(DMTL_CLASS_ID, 0)) 
    { 
        // It is std material.  does not have any submaterials
        StdMat* std = (StdMat *)mtl;
        int i;
        for(i=0;i<std->NumSubTexmaps();i++)
        {
            if (IIsAnimatedTexmap(std->GetSubTexmap(i)))
                return true;
        }
        return false;
    }

    hsGuardEnd; 
}

void hsMaterialConverter::GetUsedMaterials(plMaxNode* node, hsBitVector& used)
{
    Mtl* mtl = GetBaseMtl(node);

    used.Clear();
    if( !IsMultiMat(mtl) )
    {
        used.SetBit(0);
        return;
    }

    hsBool deleteIt = false;
    TriObject* triObj = node->GetTriObject(deleteIt);
    if( triObj )
    {
        Mesh* mesh = &(triObj->mesh);
    
        int numFaces = mesh->getNumFaces();
        int i;
        for( i = 0; i < numFaces; i++ )
        {
            Face        *face = &mesh->faces[ i ];

            used.SetBit(face->getMatID());
        }
    }
    return;
}

//// HasMaterialDiffuseOrOpacityAnimation ////////////////////////////////////
//  Returns true if the material or any of its submaterials has a diffuse
//  animation controller. Handy for those times when you need to decide on
//  a lighting model...
//  UPDATE 8.26 mcn - Now checks for opacity controllers as well

hsBool  hsMaterialConverter::HasMaterialDiffuseOrOpacityAnimation(plMaxNode* node, Mtl* mtl)
{
    hsGuardBegin( "hsMaterialConverter::HasMaterialDiffuseOrOpacityAnimation" );

    if( !mtl )
        mtl = GetBaseMtl(node);

    if( !mtl )
        return false;

    const char  *dbgName = mtl->GetName();

    // mf
    // Inserting this test here. The glaring omission was that if you had several
    // passes, each using a different material opacity, then you can't bake the
    // alpha into the vertices, so you can't go to kLiteVtxNonPreshaded (which is
    // what this function really tests to see if you can do).
    // More specifically, we can't bake material opacity into the verts if:
    //      Two or more passes use alpha blending AND have different opacity values.
    if( IsMultipassMat(mtl) )
    {
        float baseOpac = -1.f;
        Mtl* subMtl = mtl->GetSubMtl(0);
        if( subMtl->ClassID() == PASS_MTL_CLASS_ID )
        {
            plPassMtlBase* passMtl = (plPassMtlBase*)subMtl;
            if( plPassMtlBase::kBlendAlpha == passMtl->GetOutputBlend() )
                baseOpac = passMtl->GetOpacity();
        }
        int iMtl;
        for( iMtl = 1; iMtl < mtl->NumSubMtls(); iMtl++ )
        {
            if( subMtl->ClassID() == PASS_MTL_CLASS_ID )
            {
                plPassMtlBase* passMtl = (plPassMtlBase*)subMtl;
                if( (plPassMtlBase::kBlendAlpha == passMtl->GetOutputBlend())
                    &&(baseOpac != passMtl->GetOpacity()) )
                {
                    if( baseOpac >= 0 )
                        return true;
                    baseOpac = passMtl->GetOpacity();
                }
            }
        }
    }

    if( IsMultiMat( mtl ) )
    {
        hsBitVector usedSubs;
        GetUsedMaterials(node, usedSubs);

        int iMtl;
        for( iMtl = 0; iMtl < mtl->NumSubMtls(); iMtl++ )
        {
            // We have to check for nil here, because HasMaterialDif... assumes that when you pass in nil,
            // it should just grab the material on the node. In this case that's the multimat we're looping
            // through. Hellooooooo infinite loop.
            if( mtl->GetSubMtl(iMtl) && usedSubs.IsBitSet(iMtl) && HasMaterialDiffuseOrOpacityAnimation( node, mtl->GetSubMtl(iMtl) ) ) 
                return true;
        }
        return false;
    }
    else if( IsMultipassMat( mtl ) || IsCompositeMat( mtl ) )
    {
        int iMtl;
        for( iMtl = 0; iMtl < mtl->NumSubMtls(); iMtl++ )
        {
            if( mtl->GetSubMtl(iMtl) && HasMaterialDiffuseOrOpacityAnimation( node, mtl->GetSubMtl(iMtl) ) ) 
                return true;
        }
        return false;
    }
    else if ( IsParticleMat( mtl ) )
    {
        plParticleMtl *partMtl = (plParticleMtl *)mtl;
        return partMtl->GetColorController() != nil;
    }
    else if( IsHsMaxMat(mtl) || IsDecalMat(mtl) )
    { 
        // It is std material.  does not have any submaterials
        StdMat          *std = (StdMat *)mtl;
        plPassMtlBase   *passMtl = (plPassMtlBase *)mtl;
        Control         *ctl = nil;
        int             i;

        if( passMtl->GetPreshadeColorController() != nil )
            return true;
        if( passMtl->GetRuntimeColorController() != nil )
            return true;
        if( passMtl->GetOpacityController() != nil )
            return true;

        for( i = 0; i < std->NumSubTexmaps(); i++ )
        {
            if( hsControlConverter::Instance().GetControllerByName( std->GetSubTexmap( i ), TSTR( "Diffuse" ), ctl ) ) 
                return true;
            if( hsControlConverter::Instance().GetControllerByName( std->GetSubTexmap( i ), TSTR( "Color" ), ctl ) ) 
                return true;
            if( hsControlConverter::Instance().GetControllerByName( std->GetSubTexmap( i ), TSTR( "Opacity" ), ctl ) ) 
                return true;
        }
        return false;
    }

    return false;

    hsGuardEnd; 
}

//// HasEmissiveLayer ////////////////////////////////////////////////////////
//  Returns true if the any of the layers of any of the submaterials or
//  the main material are emissive.

hsBool  hsMaterialConverter::HasEmissiveLayer(plMaxNode* node, Mtl* mtl)
{
    hsGuardBegin( "hsMaterialConverter::HasEmissiveLayer" );

    if( !mtl )
        mtl = GetBaseMtl(node);

    if( !mtl )
        return false;

    const char  *dbgName = mtl->GetName();

    if( IsMultiMat( mtl ) )
    {
        hsBitVector usedSubs;
        GetUsedMaterials(node, usedSubs);

        int iMtl;
        for( iMtl = 0; iMtl < mtl->NumSubMtls(); iMtl++ )
        {
            if( usedSubs.IsBitSet(iMtl) && HasEmissiveLayer( node, mtl->GetSubMtl(iMtl) ) ) 
                return true;
        }
        return false;
    }
    else if( IsMultipassMat( mtl ) || IsCompositeMat( mtl ) )
    {
        int iMtl;
        for( iMtl = 0; iMtl < mtl->NumSubMtls(); iMtl++ )
        {
            if( HasEmissiveLayer( node, mtl->GetSubMtl(iMtl) ) ) 
                return true;
        }
        return false;
    }
    else if ( IsParticleMat( mtl ) )
    {
        plParticleMtl *partMtl = (plParticleMtl *)mtl;
        if( partMtl->GetParamBlockByID( plParticleMtl::kRefBasic )->GetInt( plParticleMtl::kNormal ) == plParticleMtl::kEmissive )
            return true;
    }
    else if( mtl->ClassID() == PASS_MTL_CLASS_ID )
    { 
        // It is std material.  does not have any submaterials
        plPassMtlBase   *passMtl = (plPassMtlBase *)mtl;

        if( passMtl->GetEmissive() )
            return true;
    }

    return false;

    hsGuardEnd; 
}

//
// returns true if the material onthis node is animated
//
hsBool hsMaterialConverter::HasAnimatedMaterial(plMaxNode* node)
{
    hsGuardBegin("hsMaterialConverter::HasAnimatedMaterial");

    return (node ? IsAnimatedMaterial(node->GetMtl()) : false);
    hsGuardEnd; 
}

Mtl* hsMaterialConverter::FindSubMtlByName(TSTR& name, Animatable* anim)
{
    hsGuardBegin("hsMaterialConverter::FindSubMtlByName");

    if( !anim || !IsMtl(anim) )
        return nil;

    Mtl* mtl = (Mtl*)anim;

    if( mtl->GetName() == name )
        return mtl;

    if( IsMultiMat(mtl) )
    {
        int i;
        for( i = 0; i < mtl->NumSubs(); i++ )
        {
            Mtl* retVal;
            if( retVal = FindSubMtlByName(name, mtl->SubAnim(i)) )
                return retVal;
        }
    }

    return nil;
    hsGuardEnd; 
}

Mtl* hsMaterialConverter::FindSceneMtlByName(TSTR& name)
{
    hsGuardBegin("hsMaterialConverter::FindSceneMtlByName");

    ReferenceTarget *scene = fInterface->GetScenePointer();

    // First look through the editor slots
    ReferenceTarget* mtlEdit;
    mtlEdit = scene->GetReference(0);

    int i;
    for( i = 0; i < mtlEdit->NumSubs(); i++ )
    {
        Mtl* mtl = FindSubMtlByName(name, mtlEdit->SubAnim(i));
        if( mtl )
            return (mtl);
    }


    // Now look through the rest of the scene
    MtlBaseLib& mtlLib = *(MtlBaseLib*)scene->GetReference(1);
    for( i = 0; i < mtlLib.Count(); i++ )
    {
        Mtl* mtl = FindSubMtlByName(name, mtlLib[i]);
        if( mtl )
            return (mtl);
    }

    return nil;
    hsGuardEnd; 
}

int hsMaterialConverter::GetMaterialArray(Mtl *mtl, plMaxNode* node, hsTArray<hsGMaterial*>& out, uint32_t multiIndex /* = 0 */)
{
    hsTArray<plExportMaterialData>* arGh = CreateMaterialArray(mtl, node, multiIndex);
    int i;
    for( i = 0; i < arGh->GetCount(); i++ )
    {
        out.Append(arGh->Get(i).fMaterial);
    }

    delete arGh;

    return out.GetCount();
}

static void GetMtlNodes(Mtl *mtl, INodeTab& nodes)
{
    if (!mtl)
        return;

    DependentIterator di(mtl);
    ReferenceMaker *rm = di.Next();
    while (rm != nil)
    {
        if (rm->SuperClassID() == BASENODE_CLASS_ID)
        {
            INode *node = (INode*)rm;
            if (node->GetMtl() == mtl)
                nodes.Append(1, &node);
        }
    }

    rm = di.Next();
}

int hsMaterialConverter::GetMaterialArray(Mtl *mtl, hsTArray<hsGMaterial*>& out, uint32_t multiIndex /* = 0 */)
{
    INodeTab nodes;
    GetMtlNodes(mtl, nodes);

    for (int i = 0; i < nodes.Count(); i++)
    {
        hsTArray<hsGMaterial*> tempOut;
        GetMaterialArray(mtl, (plMaxNode*)nodes[i], tempOut, multiIndex);

        for (int j = 0; j < tempOut.GetCount(); j++)
        {
            if (out.Find(tempOut[j]) == out.kMissingIndex)
                out.Append(tempOut[j]);
        }
    }

    return out.GetCount();
}

// Grab all the hsGMaterials that have been created as a result of converting mtl
void hsMaterialConverter::CollectConvertedMaterials(Mtl *mtl, hsTArray<hsGMaterial *>& out)
{
    int i;
    for (i = 0; i < fDoneMaterials.GetCount(); i++)
    {
        const DoneMaterialData &dmd = fDoneMaterials.Get(i);
        if (dmd.fMaxMaterial == mtl)
            out.Append(dmd.fHsMaterial);
    }
}

plClothingItem *hsMaterialConverter::GenerateClothingItem(plClothingMtl *mtl, const plLocation &loc)
{
    plString clothKeyName;
    plClothingItem *cloth = new plClothingItem();
    cloth->SetName(mtl->GetName());
    cloth->fSortOrder = (mtl->GetDefault() ? 0 : 1);

    const char *accName = mtl->GetForcedAccessoryName();
    if (accName && strcmp(accName, ""))
        cloth->fAccessoryName = hsStrcpy(accName);
    
    Color tint1 = mtl->GetDefaultTint1();
    Color tint2 = mtl->GetDefaultTint2();
    cloth->fDefaultTint1[0] = tint1.r * 255;
    cloth->fDefaultTint1[1] = tint1.g * 255;
    cloth->fDefaultTint1[2] = tint1.b * 255;
    cloth->fDefaultTint2[0] = tint2.r * 255;
    cloth->fDefaultTint2[1] = tint2.g * 255;
    cloth->fDefaultTint2[2] = tint2.b * 255;
    
    clothKeyName = plString::Format("CItm_%s", cloth->fName);
    hsgResMgr::ResMgr()->NewKey(clothKeyName, cloth, loc);
    
    plNodeRefMsg* nodeRefMsg = new plNodeRefMsg(plKeyFinder::Instance().FindSceneNodeKey(loc), 
                                                plNodeRefMsg::kOnRequest, -1, plNodeRefMsg::kGeneric);
    hsgResMgr::ResMgr()->AddViaNotify(cloth->GetKey(), nodeRefMsg, plRefFlags::kActiveRef);

    mtl->InitTilesets();
    cloth->fTileset = mtl->GetTilesetIndex();
    plClothingTileset *tileset = mtl->fTilesets.Get(cloth->fTileset); 
    int i, j;
    for (i = 0; i < tileset->fElements.GetCount(); i++)
    {
        for (j = 0; j < plClothingElement::kLayerMax; j++)
        {
            uint32_t clipLevels;
            uint32_t startWidth;
            char *elementName = tileset->fElements.Get(i)->fName;
            plPlasmaMAXLayer *layer = (plPlasmaMAXLayer *)mtl->GetTexmap(i, j);
            if (layer == nil || layer->GetPBBitmap() == nil)
                continue;

            const char *texName = layer->GetPBBitmap()->bi.Name();

            for (clipLevels = 0, startWidth = layer->GetPBBitmap()->bi.Width(); 
                 startWidth > tileset->fElements.Get(i)->fWidth;
                 clipLevels++, startWidth >>= 1);

            plMipmap *tex = plMipmap::ConvertNoRef(plLayerConverter::Instance().CreateSimpleTexture(texName, loc, clipLevels));
            if (tex == nil)
            {
                if (fErrorMsg->Set(!(fWarned & kWarnedMissingClothingTexture), mtl->GetName(), 
                    "Unable to create texture %s. This clothing item won't look right.", 
                    texName).CheckAskOrCancel() )
                {
                    fWarned |= kWarnedMissingClothingTexture;
                }
                continue;
            }
            plElementRefMsg *eMsg = new plElementRefMsg(cloth->GetKey(), plRefMsg::kOnCreate, i, -1, elementName, j);
            hsgResMgr::ResMgr()->AddViaNotify(tex->GetKey(), eMsg, plRefFlags::kActiveRef);
        }
    }
    mtl->ReleaseTilesets();
    
    plPlasmaMAXLayer *layer = (plPlasmaMAXLayer *)mtl->GetThumbnail();
    plMipmap *thumbnail = nil;
    PBBitmap *pbbm = nil;
    if (layer != nil)
    {
        char texName[ 512 ];
        if( layer->GetBitmapFileName( texName, sizeof( texName ) ) )
            thumbnail = plMipmap::ConvertNoRef(plLayerConverter::Instance().CreateSimpleTexture(texName, loc, 0, plBitmap::kForceOneMipLevel));
    }
    if (thumbnail != nil)
    {
        plGenRefMsg *msg= new plGenRefMsg(cloth->GetKey(), plRefMsg::kOnCreate, -1, -1);
        hsgResMgr::ResMgr()->AddViaNotify(thumbnail->GetKey(), msg, plRefFlags::kActiveRef); 
    }
    cloth->fDescription = hsStrcpy(mtl->GetDescription());
    cloth->fCustomText = hsStrcpy(mtl->GetCustomText());

    return cloth;
}

static int ICompareBaseLayerTexture(const hsMaterialConverter::DoneMaterialData* one, const hsMaterialConverter::DoneMaterialData* two)
{
    const plLayerInterface* oneLay = one->fHsMaterial->GetLayer(0);
    const plLayerInterface* twoLay = two->fHsMaterial->GetLayer(0);
    const plBitmap* oneTex = oneLay->GetTexture();
    const plBitmap* twoTex = twoLay->GetTexture();

    if( !oneTex && !twoTex )
        return 0;
    if( oneTex && !twoTex )
        return 1;
    if( !oneTex && twoTex )
        return -1;

    return oneTex->GetKey()->GetName().Compare(twoTex->GetKey()->GetName(), plString::kCaseInsensitive);
}

static int IIsAnimatedLayer(const plLayerInterface* lay)
{
    return nil == plLayer::ConvertNoRef(lay);
}

static int ICompareColors(const hsColorRGBA& one, const hsColorRGBA& two)
{
    int oneR = int(one.r * 256.f);
    int oneG = int(one.g * 256.f);
    int oneB = int(one.b * 256.f);
    int twoR = int(two.r * 256.f);
    int twoG = int(two.g * 256.f);
    int twoB = int(two.b * 256.f);

    int powerOne = oneR + oneG + oneB;
    int powerTwo = twoR + twoG + twoB;

    if( powerOne < powerTwo )
        return -1;
    if( powerOne > powerTwo )
        return 1;

    if( oneR < twoR )
        return -1;
    if( oneR > twoR )
        return 1;

    if( oneG < twoG )
        return -1;
    if( oneG > twoG )
        return 1;

    if( oneB < twoB )
        return -1;
    if( oneB > twoB )
        return 1;

    return 0;
}

static int ICompareDoneLayers(const plLayerInterface* one, const plLayerInterface* two)
{
    int retVal;

    if( one == two )
        return 0;

    if( one->GetTexture() && !two->GetTexture() )
        return 1;
    if( !one->GetTexture() && two->GetTexture() )
        return -1;

    if( one->GetTexture() && two->GetTexture() )
    {
        retVal = one->GetTexture()->GetKey()->GetName().Compare(two->GetTexture()->GetKey()->GetName(), plString::kCaseInsensitive);
        if( retVal < 0 )
            return -1;
        else if( retVal > 0 )
            return 1;
    }

    retVal =  int32_t(one->GetBlendFlags()) - int32_t(two->GetBlendFlags());
    if( retVal < 0 )
        return -1;
    else if( retVal > 0 )
        return 1;

    retVal =  int32_t(one->GetZFlags()) - int32_t(two->GetZFlags());
    if( retVal < 0 )
        return -1;
    else if( retVal > 0 )
        return 1;

    retVal =  int32_t(one->GetClampFlags()) - int32_t(two->GetClampFlags());
    if( retVal < 0 )
        return -1;
    else if( retVal > 0 )
        return 1;

    retVal =  int32_t(one->GetMiscFlags()) - int32_t(two->GetMiscFlags());
    if( retVal < 0 )
        return -1;
    else if( retVal > 0 )
        return 1;

    retVal =  int32_t(one->GetShadeFlags()) - int32_t(two->GetShadeFlags());
    if( retVal < 0 )
        return -1;
    else if( retVal > 0 )
        return 1;

    retVal = ICompareColors(one->GetAmbientColor(), two->GetAmbientColor());
    if( retVal < 0 )
        return -1;
    else if( retVal > 0 )
        return 1;

    retVal = ICompareColors(one->GetPreshadeColor(), two->GetPreshadeColor());
    if( retVal < 0 )
        return -1;
    else if( retVal > 0 )
        return 1;

    retVal = ICompareColors(one->GetRuntimeColor(), two->GetRuntimeColor());
    if( retVal < 0 )
        return -1;
    else if( retVal > 0 )
        return 1;

    retVal = ICompareColors(one->GetSpecularColor(), two->GetSpecularColor());
    if( retVal < 0 )
        return -1;
    else if( retVal > 0 )
        return 1;

    if( one->GetSpecularPower() < two->GetSpecularPower() )
        return -1;
    if( one->GetSpecularPower() > two->GetSpecularPower() )
        return 1;

    if( one->GetLODBias() < two->GetLODBias() )
        return -1;
    if( one->GetLODBias() > two->GetLODBias() )
        return 1;

    if( one->GetOpacity() < two->GetOpacity() )
        return -1;
    if( one->GetOpacity() > two->GetOpacity() )
        return 1;

    if( one->GetUVWSrc() < two->GetUVWSrc() )
        return -1;
    if( one->GetUVWSrc() > two->GetUVWSrc() )
        return 1;

    if( one->GetTexture() && two->GetTexture() )
    {
        if( one->GetTransform() != two->GetTransform() )
        {
            // Okay, they're not equal. Greater/Lesser doesn't make much
            // sense in this context, so we just need some arbitrary but 
            // consistent comparison. So if the transforms aren't equal,
            // the one on the layer with a larger pointer value is greater.
            if( one > two )
                return 1;
            else
                return -1;
        }
    }

    if( IIsAnimatedLayer(one) || IIsAnimatedLayer(two) )
    {
        // Same deal as transform. If either is animated, then for
        // the purposes here (whether one can replace the other), they
        // aren't interchangeable. Even if they have the same animation,
        // they need to stay independently playable.
        if( one > two )
            return 1;
        else
            return -1;
    }

    return 0;
}

static int ICompareDoneMats(const void *arg1, const void *arg2)
{
    const hsMaterialConverter::DoneMaterialData* one = *(const hsMaterialConverter::DoneMaterialData**)arg1;
    const hsMaterialConverter::DoneMaterialData* two = *(const hsMaterialConverter::DoneMaterialData**)arg2;

    hsGMaterial* oneMat = one->fHsMaterial;
    hsGMaterial* twoMat = two->fHsMaterial;

    // compare the base layers
    // First compare the textures. If those are the same, these two materials are very much alike.
    // This will (quickly) weed out 99% of the non-equivalent materials before the more expensive checks.
    plLayerInterface* oneLay = oneMat->GetLayer(0);
    plLayerInterface* twoLay = twoMat->GetLayer(0);
    int retVal = ICompareBaseLayerTexture(one, two);
    if( retVal > 0 )
        return 1;
    if( retVal < 0 )
        return -1;

    // Check for lightmap compatible-ness.
    // The case we're looking for is if:
    //      two different nodes are using the same material,
    // &&   both are lightmapped
    // &&   either 
    //          they are different lightmap components
    //      ||  the lightmap component doesn't want to share.
    // If true, we want to ensure these two materials don't get combined.
    // Since this function is used for material sorting, we'll (arbitrarily)
    // return the comparison of owner node pointers as our consistent result.
    plMaxNode* oneNode = one->fNode;
    plMaxNode* twoNode = two->fNode;
    if( oneNode && twoNode && (oneNode != twoNode) )
    {
        plLightMapComponent* oneLM = oneNode->GetLightMapComponent();
        plLightMapComponent* twoLM = twoNode->GetLightMapComponent();
        if( oneLM != twoLM )
        {
            return oneNode > twoNode ? 1 : -1;
        }
        if( oneLM )
        {
            if( !oneLM->GetShared() ) // and therefore twoLM, since they're equal
            {
                return oneNode > twoNode ? 1 : -1;
            }
        }
    }

    if( oneMat == twoMat )
        return 0;

    // Now compare everything else about the base layer.
    retVal = ICompareDoneLayers(oneMat->GetLayer(0), twoMat->GetLayer(0));
    if( retVal < 0 )
        return -1;
    else if( retVal > 0 )
        return 1;

    // base layers the same, go up a layer at a time. Non-existence of a layer is < any existent layer
    int i;
    for( i = 1; i < oneMat->GetNumLayers(); i++ )
    {
        if( twoMat->GetNumLayers() <= i )
            return 1;

        retVal = ICompareDoneLayers(oneMat->GetLayer(i), twoMat->GetLayer(i));
        if( retVal < 0 )
            return -1;
        else if( retVal > 0 )
            return 1;
    }
    if( oneMat->GetNumLayers() < twoMat->GetNumLayers() )
        return -1;

    return 0;
}

void hsMaterialConverter::IPrintDoneMat(hsStream* stream, const char* prefix, DoneMaterialData* doneMat)
{
    if( doneMat->fOwnedCopy )
        stream->WriteString("Unique ");
    stream->WriteString(prefix);

    char buff[512];
    sprintf(buff, "%s\n", doneMat->fMaxMaterial ? doneMat->fMaxMaterial->GetName() : "BLANK");
    stream->WriteString(buff);

    sprintf(buff, "\t\t%d Layers\n", doneMat->fHsMaterial->GetNumLayers());
    stream->WriteString(buff);

    int i;
    for( i = 0; i < doneMat->fHsMaterial->GetNumLayers(); i++ )
    {
        const plLayerInterface* layer = doneMat->fHsMaterial->GetLayer(i);

        const char* blendMode = "error";
        switch(layer->GetBlendFlags() & hsGMatState::kBlendMask)
        {
        case hsGMatState::kBlendAlpha:
            blendMode = "Alpha";
            break;
        case hsGMatState::kBlendMult:
            blendMode = "Mult";
            break;
        case hsGMatState::kBlendAdd:
            blendMode = "Add";
            break;
        case hsGMatState::kBlendAddColorTimesAlpha:
            blendMode = "AddColorTimesAlpha";
            break;
        case hsGMatState::kBlendDetail:
            blendMode = "Detail";
            break;
        case hsGMatState::kBlendMADD:
            blendMode = "MADD";
            break;
        case hsGMatState::kBlendDot3:
            blendMode = "Dot3";
            break;
        default:
            blendMode = "Opaque";
            break;
        }

        sprintf(buff, "\t\tLayer %d [%s]\n", i, IIsAnimatedLayer(layer) ? "Animated" : "Static");
        stream->WriteString(buff);

        sprintf(buff, "\t\t\t%s [B%#08x Z%#08x C%#08x M%#08x S%08x]\n", blendMode, 
            layer->GetBlendFlags(), 
            layer->GetZFlags(),
            layer->GetClampFlags(),
            layer->GetMiscFlags(),
            layer->GetShadeFlags());
        stream->WriteString(buff);

        sprintf(buff, "\t\t\tAmbient(%f,%f,%f) Preshade(%f,%f,%f)\n",
            layer->GetAmbientColor().r,
            layer->GetAmbientColor().g,
            layer->GetAmbientColor().b,
            layer->GetPreshadeColor().r,
            layer->GetPreshadeColor().g,
            layer->GetPreshadeColor().b);
        stream->WriteString(buff);

        sprintf(buff, "\t\t\tColor(%f,%f,%f) Opacity(%f) UVWSrc(%x)\n",
            layer->GetRuntimeColor().r,
            layer->GetRuntimeColor().g,
            layer->GetRuntimeColor().b,
            layer->GetOpacity(),
            layer->GetUVWSrc());
        stream->WriteString(buff);

        sprintf(buff, "\t\t\tSpec(%f,%f,%f) Power(%f) LODBias(%f)\n",
            layer->GetSpecularColor().r,
            layer->GetSpecularColor().g,
            layer->GetSpecularColor().b,
            layer->GetSpecularPower(),
            layer->GetLODBias());
        stream->WriteString(buff);

        sprintf(buff, "\t\t\tTexture %s\n", layer->GetTexture() && layer->GetTexture()->GetKey()
            ? layer->GetTexture()->GetKey()->GetName().s_str("None")
            : "None");
        stream->WriteString(buff);

        if( layer->GetTransform().fFlags & hsMatrix44::kIsIdent )
        {
            sprintf(buff, "\t\t\tXForm = None\n");
            stream->WriteString(buff);
        }
        else
        {
            sprintf(buff, "\t\t\tXForm = \t{ {%f,%f,%f,%f}, \n\t\t\t\t\t{%f,%f,%f,%f}, \n\t\t\t\t\t{%f,%f,%f,%f} }\n",
                layer->GetTransform().fMap[0][0],
                layer->GetTransform().fMap[0][1],
                layer->GetTransform().fMap[0][2],
                layer->GetTransform().fMap[0][3],

                layer->GetTransform().fMap[1][0],
                layer->GetTransform().fMap[1][1],
                layer->GetTransform().fMap[1][2],
                layer->GetTransform().fMap[1][3],

                layer->GetTransform().fMap[2][0],
                layer->GetTransform().fMap[2][1],
                layer->GetTransform().fMap[2][2],
                layer->GetTransform().fMap[2][3]);
            stream->WriteString(buff);
        }
    }
}

hsBool hsMaterialConverter::IEquivalent(DoneMaterialData* one, DoneMaterialData* two)
{
    if( one->fOwnedCopy || two->fOwnedCopy )
        return false;

    return ICompareDoneMats(&one, &two) == 0;
}

void hsMaterialConverter::ISortDoneMaterials(hsTArray<DoneMaterialData*>& doneMats)
{
    doneMats.SetCount(fDoneMaterials.GetCount());
    int i;
    for( i = 0; i < fDoneMaterials.GetCount(); i++ )
        doneMats[i] = &fDoneMaterials[i];


    void* arr = doneMats.AcquireArray();
    qsort((void*)arr, doneMats.GetCount(), sizeof(DoneMaterialData*), ICompareDoneMats);
}

void hsMaterialConverter::IGenMaterialReport(const char* path)
{
    hsTArray<DoneMaterialData*> doneMats;
    ISortDoneMaterials(doneMats);

    IPrintDoneMaterials(path, doneMats);
}

void hsMaterialConverter::IPrintDoneMaterials(const char* path, hsTArray<DoneMaterialData*>& doneMats)
{
    TSTR maxFileTstr = GetCOREInterface()->GetCurFileName();
    char maxFile[256];
    hsStrncpy(maxFile, maxFileTstr, 128);
    char *dot = strrchr(maxFile, '.');
    if (dot)
        *dot = 0;

    char fileName[512];
    if( path[hsStrlen(path)-1] == '\\' )
    {
        sprintf(fileName, "%slog\\mat_%s.log", path, maxFile);
    }
    else
    {
        sprintf(fileName, "%s\\log\\mat_%s.log", path, maxFile);
    }

    hsUNIXStream stream;
    if( !stream.Open(fileName, "wt") )
    {
        // We may not have a \log folder. If that failed, try
        // putting it in the \dat folder. If that doesn't work,
        // just quietly give up.
        if( path[hsStrlen(path)-1] == '\\' )
        {
            sprintf(fileName, "%sdat\\mat_%s.log", path, maxFile);
        }
        else
        {
            sprintf(fileName, "%s\\dat\\mat_%s.log", path, maxFile);
        }
        if( !stream.Open(fileName, "wt") )
            return;
    }

    stream.WriteString(maxFile);
    stream.WriteString("\n===============================================\n===============================================\n");

    if( !doneMats.GetCount() )
    {
        char buff[256];
        sprintf(buff, "");
        stream.WriteString("No Materials Generated\n");
        stream.Close();
        return;
    }

    char pref[32];
    sprintf(pref, "%d\t", 0);
    IPrintDoneMat(&stream, pref, doneMats[0]);

    bool lastWasDup = false;
    int dupSets = 0;
    int duplicates = 0;
    int uniques = 0;
    int i;
    for( i = 1; i < doneMats.GetCount(); i++ )
    {
        if( IEquivalent(doneMats[i], doneMats[i-1]) )
        {
            if( !lastWasDup )
            {
                dupSets++;
                lastWasDup = true;
            }
            duplicates++;
            sprintf(pref, "==%d\t", i);
        }
        else if( !ICompareBaseLayerTexture(doneMats[i], doneMats[i-1]) )
        {
            sprintf(pref, "~~%d\t", i);
            lastWasDup = false;
        }
        else
        {
            sprintf(pref, "%d\t", i);
            lastWasDup = false;
        }
        if( doneMats[i]->fOwnedCopy )
            uniques++;

        IPrintDoneMat(&stream, pref, doneMats[i]);

    }
    char buff[256];
    sprintf(buff, "\n===================================================================\n");
    stream.WriteString(buff);
    sprintf(buff, "%d sets of duplicates, %d total duplicate count\n", dupSets, duplicates);
    stream.WriteString(buff);

    sprintf(buff, "System generated duplicates:\n");
    stream.WriteString(buff);

    sprintf(buff, "Gameplay forced unique - %d\n", uniques);
    stream.WriteString(buff);

    sprintf(buff, "RT:%d, UV:%d, AL:%d, FD:%d\n", dupCuzRT, dupCuzNumUV, dupCuzAlphaLayer, dupCuzFade);
    stream.WriteString(buff);

    sprintf(buff, "\nThank you, and have a lovely day.\n");
    stream.WriteString(buff);

    stream.Close();
}

hsMaterialConverter::DoneMaterialData* hsMaterialConverter::IFindDoneMaterial(DoneMaterialData& done)
{
    int i;
    for( i = 0; i < fDoneMaterials.GetCount(); i++ )
    {
        if( IEquivalent(&fDoneMaterials[i], &done) )
        {
            return &fDoneMaterials[i];
        }
    }
    return nil;
}

plMipmap *hsMaterialConverter::GetStaticColorTexture(Color c, plLocation &loc)
{
    uint32_t colorHex = MakeUInt32Color(c.r, c.g, c.b, 1.f);
    plString texName = plString::Format("StaticColorTex_4x4_%X", colorHex);

    int w = 4;
    int h = 4;

    plMipmap *texture = plBitmapCreator::Instance().CreateBlankMipmap(w, h, plMipmap::kARGB32Config, 1, texName, loc );

    // set the color data
    uint32_t* pix = (uint32_t*)texture->GetImage();
    int x, y;

    for (y = 0; y < h; y++)
    {
        for (x = 0; x < w; x++)
        {
            *pix++ = colorHex;
        }
    }

    return texture;
}
