You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

5140 lines
164 KiB

/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Additional permissions under GNU GPL version 3 section 7
If you modify this Program, or any covered work, by linking or
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK,
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK
(or a modified version of those libraries),
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA,
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the
licensors of this Program grant you additional
permission to convey the resulting work. Corresponding Source for a
non-source form of such a combination shall include the source code for
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered
work.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
#include "HeadSpin.h"
#include "plgDispatch.h"
#include "hsExceptionStack.h"
#include "hsResMgr.h"
#include "hsStringTokenizer.h"
#include "hsTemplates.h"
#include "MaxComponent/plComponent.h"
#include <cmath>
#include <cfloat>
#include <commdlg.h>
#include <bmmlib.h>
#include <istdplug.h>
#include <pbbitmap.h>
#include <stdmat.h>
#pragma hdrstop
#include "hsMaterialConverter.h"
#include "plLayerConverter.h"
#include "MaxComponent/plMaxAnimUtils.h"
#include "plResMgr/plKeyFinder.h"
#include "pnKeyedObject/plUoid.h"
#include "hsMaxLayerBase.h"
#include "MaxExport/plErrorMsg.h"
#include "plSurface/hsGMaterial.h"
#include "pnSceneObject/plSceneObject.h"
#include "UserPropMgr.h"
#include "hsConverterUtils.h"
#include "hsControlConverter.h"
#include "MaxMain/plMaxNode.h"
#include "MaxMain/MaxCompat.h"
#include "plInterp/plController.h"
#include "plSurface/plLayerInterface.h"
#include "plSurface/plLayer.h"
#include "plSurface/plLayerAnimation.h"
#include "plGImage/plMipmap.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 "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(bool 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();
}
bool hsMaterialConverter::ForceNoUvsFlatten(plMaxNode* node)
{
hsGuardBegin("hsMaterialConverter::ForceNoUvsFlatten");
return IsMultiMat(GetBaseMtl(node));
hsGuardEnd;
}
bool 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};
bool 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];
snprintf(suff, arrsize(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 = plFormat("{}_{}_{}", oldLayer->GetKeyName(), 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;
bool 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??
//
bool hsMaterialConverter::IsMultiMat(Mtl *m)
{
hsGuardBegin("hsMaterialConverter::IsMultiMat");
if (m == NULL)
{
return false;
}
return (m->ClassID() == Class_ID(MULTI_CLASS_ID,0));
hsGuardEnd;
}
bool hsMaterialConverter::IsHsMaxMat(Mtl *m)
{
hsGuardBegin("hsMaterialConverter::IsHsMaxMat");
if (m==NULL)
{
return false;
}
return (m->ClassID() == PASS_MTL_CLASS_ID);
hsGuardEnd;
}
bool hsMaterialConverter::IsMultipassMat(Mtl *m)
{
hsGuardBegin("hsMaterialConverter::IsHsMaxMat");
if (m==NULL)
{
return false;
}
return (m->ClassID() == MULTIMTL_CLASS_ID);
hsGuardEnd;
}
bool hsMaterialConverter::IsDecalMat(Mtl *m)
{
hsGuardBegin("hsMaterialConverter::IsDecalMat");
if (m==NULL)
{
return false;
}
return (m->ClassID() == DECAL_MTL_CLASS_ID);
hsGuardEnd;
}
bool hsMaterialConverter::IsCompositeMat(Mtl *m)
{
hsGuardBegin("hsMaterialConverter::IsCompositeMat");
if (m==NULL)
{
return false;
}
return (m->ClassID() == COMP_MTL_CLASS_ID);
hsGuardEnd;
}
bool hsMaterialConverter::IsParticleMat(Mtl *m)
{
hsGuardBegin("hsMaterialConverter::IsParticleMat");
if (m==NULL)
{
return false;
}
return (m->ClassID() == PARTICLE_MTL_CLASS_ID);
hsGuardEnd;
}
bool hsMaterialConverter::IsClothingMat(Mtl *m)
{
hsGuardBegin("hsMaterialConverter::IsClothingMat");
if (m==NULL)
{
return false;
}
return (m->ClassID() == CLOTHING_MTL_CLASS_ID);
hsGuardEnd;
}
bool hsMaterialConverter::IsTwoSided(Mtl* m, int iSubMtl)
{
if( !m )
return false;
return 0 != (m->Requirements(iSubMtl) & MTLREQ_2SIDE);
}
bool hsMaterialConverter::HasAnimatedTextures(Texmap* texMap)
{
hsGuardBegin("hsMaterialConverter::HasAnimatedTextures");
return false;
hsGuardEnd;
}
bool hsMaterialConverter::IsAVILayer(Texmap* texMap)
{
hsGuardBegin("hsMaterialConverter::IsAVILayer");
return false;
hsGuardEnd;
}
bool hsMaterialConverter::IsQTLayer(Texmap* texMap)
{
hsGuardBegin("hsMaterialConverter::IsQTLayer");
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();
bool makeAlphaLayer = node->AlphaHackLayersNeeded(multiIndex) > 0;
bool enviro = fConverterUtils.IsEnvironHolder(node);
plString name;
if (maxMaterial)
name = plString::FromUtf8(maxMaterial->GetName());
else
name = "nil";
/// Get the material
bool 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);
}
bool isMultipassMat = IsMultipassMat( maxMaterial );
bool 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 = plFormat("{}_{}_0", node->GetName(), baseTex->GetName());
// 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 = plFormat("{}_{}_0_AH", node->GetName(), baseTex->GetName());
// 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 = plFormat("{}_{}_{}", 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 = plFormat("{}_{}_{}_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, bool 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
{
bool forceCopy = hsControlConverter::Instance().OwnsMaterialCopy(node);
if( !forceCopy )
forceCopy = node->GetForceMaterialCopy();
if( !forceCopy )
forceCopy = IMustBeUniqueMaterial( mtl );
bool runtimeLit = node->GetRunTimeLight();
Mtl *bMtl = GetBaseMtl(node);
bool 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;
}
//
// 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 + "_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;
}
bool hsMaterialConverter::IsMatchingDoneMaterial(DoneMaterialData *dmd,
Mtl *mtl, bool isMultiMat, uint32_t subMtlFlags, bool forceCopy, bool runtimeLit,
plMaxNode *node, int numUVChannels, bool 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, bool isMultiMat,
bool forceCopy, bool runtimeLit, uint32_t subMtlFlags, int numUVChannels,
bool 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(plFormat("{}_DefMat", node->GetName()), hMat, loc);
plLayer *layer = new plLayer;
layer->InitToDefault();
hsgResMgr::ResMgr()->NewKey(hMat->GetKeyName() + "_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, bool useU /* = true */)
{
plString texName = (useU ? "ALPHA_BLEND_FILTER_U2ALPHA_TRANS_64x4"
: "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() + "_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,
bool 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,
bool 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, bool 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;
bool firstUsedLayer = true;
bool 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(plFormat("{}_{}", name, subMtlFlags), mat, node->GetLocation());
int multiIndex = IFindSubIndex(node, mtl);
bool needAlphaHack = node->AlphaHackLayersNeeded(multiIndex) > 0;
bool canWriteAlpha = ((plCompositeMtl *)mtl)->CanWriteAlpha() >= 0;
int bitMask = 1;
int i;
for (i = 0; i < mtl->NumSubMtls(); i++)
{
Mtl *subMtl = nil;
bool usingSubMtl = (i == 0 || pb->GetInt(kCompOn, 0, i - 1));
if ((bitMask & subMtlFlags) != 0 && usingSubMtl)
{
plString pref = plFormat("{}_{}", mat->GetKeyName(), 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)
{
bool 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)
{
bool 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)))
{
bool 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->GetKeyName());
}
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;
bool 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 = plFormat("{}_anim_{}", name, 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 = 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());
}
}
/*
bool 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;
plFileName fileName = bi->Name();
plAnimStealthNode* stealth = IGetEntireAnimation(mtl);
plString ext = fileName.GetFileExt();
bool isAvi = (ext.CompareI("avi") == 0);
if (isAvi)
{
plFileName movieName = plFileName::Join("avi", fileName.GetFileName());
plLayerMovie* movieLayer = nil;
plString moviePostfix;
if (isAvi)
{
movieLayer = new plLayerAVI;
moviePostfix = "_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 = plFormat("{}_anim_{}", name, 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 ) ? "_LayerAnim_"
: ( plString("_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 = plFormat("{}_anim_{}", name, 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 ) ? "_anim"
: ( plString("_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;
}
bool 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
bool 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);
bool 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 = (float)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);
}
bool 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);
bool 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...
//
//// 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.
bool 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())
bool hsMaterialConverter::IMustBeUniqueLayer( Texmap *layer )
{
plPlasmaMAXLayer *plasmaLayer = plPlasmaMAXLayer::GetPlasmaMAXLayer( layer );
if( plasmaLayer != nil )
return plasmaLayer->MustBeUnique();
return false;
}
bool 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;
}
bool 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 = plFormat("{}_funkRamp", prevLay->GetKeyName());
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 ? "FunkyRampAdd" : "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 = plFormat("{}_funkRamp", prevLay->GetKeyName());
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);
}
bool 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);
}
bool 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) )
{
bool baseFunky = false;
bool 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;
}
bool hsMaterialConverter::IsBumpLayer(Texmap* texMap)
{
if( texMap
&& (texMap->ClassID() == LAYER_TEX_CLASS_ID)
&& !strncmp(texMap->GetName(), kSecretBumpSign, strlen(kSecretBumpSign)) )
{
return true;
}
return false;
}
bool 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;
}
bool 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 = "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 = plFormat("{}_DU_BumpLut", nameBase);
break;
case hsGMatState::kMiscBumpDv:
name = plFormat("{}_DV_BumpLut", nameBase);
break;
case hsGMatState::kMiscBumpDw:
name = plFormat("{}_DW_BumpLut", nameBase);
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.
bool 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)->GetKeyName();
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;
}
bool hsMaterialConverter::ClearDoneMaterials(plMaxNode* node)
{
hsGuardBegin("hsMaterialConverter::ClearDoneMaterials");
Mtl *mtl = GetBaseMtl(node);
int isMultiMat = IsMultiMat(mtl);
if (isMultiMat)
{
bool 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;
}
bool hsMaterialConverter::IClearDoneMaterial(Mtl* mtl, plMaxNode* node)
{
hsGuardBegin("hsMaterialConverter::IClearDoneMaterial");
bool 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)
static BMM_Color_64 ICubeSample(plErrorMsg* const msg, 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));
msg->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(fErrorMsg, bitmap, phi, theta);
}
bm->PutPixels(0,j, bm->Width(), pb);
}
hsGuardEnd;
}
bool 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;
}
bool 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;
}
bool 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) || IsQTLayer(texmap) || ITextureTransformIsAnimated(texmap))
return true;
return false;
hsGuardEnd;
}
//
// returns true if this material is animated
//
bool 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;
}
bool 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
bool 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 = (float)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 = (float)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.
bool 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
//
bool 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((const char *)mtl->GetName());
cloth->fSortOrder = (mtl->GetDefault() ? 0 : 1);
const char *accName = mtl->GetForcedAccessoryName();
if (accName && strcmp(accName, ""))
cloth->fAccessoryName = accName;
Color tint1 = mtl->GetDefaultTint1();
Color tint2 = mtl->GetDefaultTint2();
cloth->fDefaultTint1[0] = (uint8_t)(tint1.r * 255.f);
cloth->fDefaultTint1[1] = (uint8_t)(tint1.g * 255.f);
cloth->fDefaultTint1[2] = (uint8_t)(tint1.b * 255.f);
cloth->fDefaultTint2[0] = (uint8_t)(tint2.r * 255.f);
cloth->fDefaultTint2[1] = (uint8_t)(tint2.g * 255.f);
cloth->fDefaultTint2[2] = (uint8_t)(tint2.b * 255.f);
clothKeyName = plFormat("CItm_{}", 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;
plString 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->GetKeyName().Compare(twoTex->GetKeyName(), 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()->GetKeyName().Compare(two->GetTexture()->GetKeyName(), 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()->GetKeyName().c_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);
}
}
}
bool 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[strlen(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[strlen(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 = plFormat("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;
}