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.

1614 lines
46 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/>.
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==*/
//#define MF_NEW_RGC
#include "hsTypes.h"
#include "Max.h"
#include "dummy.h"
#include "notify.h"
#include "plLightMapGen.h"
#include "../plGImage/plMipmap.h"
#include "../MaxMain/plMaxNode.h"
#include "../MaxExport/plErrorMsg.h"
#include "plRenderGlobalContext.h"
#include "plMaxLightContext.h"
#include "../plSurface/plLayer.h"
#include "../plSurface/hsGMaterial.h"
#include "../MaxMain/plPluginResManager.h"
#include "../plDrawable/plGeometrySpan.h"
#include "hsFastMath.h"
#include "hsControlConverter.h"
#include "plBitmapCreator.h"
#include "../pnKeyedObject/plKey.h"
#include "../plResMgr/plKeyFinder.h"
#include "../plResMgr/plPageInfo.h"
#include "../plMessage/plLayRefMsg.h"
#include "../plMessage/plMatRefMsg.h"
#include "../MaxComponent/plLightMapComponent.h"
#include "../plGImage/hsCodecManager.h"
#include "../plAgeDescription/plAgeDescription.h"
static plLightMapGen theLMG;
static const float kBlurMapRange = 20.f;
#ifdef MF_NEW_RGC
void getRGC(void* param, NotifyInfo* info)
{
if( info->intcode == NOTIFY_PRE_RENDERFRAME )
{
plLightMapGen* lmg = (plLightMapGen*)param;
RenderGlobalContext* rgc = (RenderGlobalContext*)info->callParam;
lmg->SetRGC(rgc);
}
}
#endif // MF_NEW_RGC
#ifndef MF_NEW_RGC
#define MF_NO_RAY_SHADOW
#endif // MF_NEW_RGC
#define MF_NO_SHADOW_BLUR
#if defined(MF_NEW_RGC) && !defined(MF_NO_SHADOW_BLUR)
#define MF_NO_SHADOW_BLUR
#endif // MF_NEW_RGC
class LMGScanPoint
{
public:
hsScalar fU;
hsPoint3 fBary;
};
class LMGScanlineData
{
public:
LMGScanlineData() : fEmpty(true) {}
hsBool fEmpty;
LMGScanPoint fNear;
LMGScanPoint fFar;
};
static int kDefaultSize = 64;
static UInt32 MakeUInt32Color(float r, float g, float b, float a)
{
return (UInt32(a * 255.9f) << 24)
|(UInt32(r * 255.9f) << 16)
|(UInt32(g * 255.9f) << 8)
|(UInt32(b * 255.9f) << 0);
}
plLightMapGen& plLightMapGen::Instance()
{
return theLMG;
}
#ifdef MF_NEW_RGC
// Don't call this ever ever ever. I mean really. Never.
void plLightMapGen::SetRGC(RenderGlobalContext* rgc)
{
fRGC = rgc;
}
#endif // MF_NEW_RGC
plLightMapGen::plLightMapGen()
: fWidth(64),
fHeight(64),
fScale(1.f),
fUVWSrc(-1),
fMapRange(-1.f),
fInterface(nil),
fRenderer(nil),
fRecalcLightMaps(true),
fRGC(nil),
fRP(nil)
{
fWidth = kDefaultSize;
fHeight = kDefaultSize;
}
plLightMapGen::~plLightMapGen()
{
Close();
}
// Set up the structures we'll need to compute the lighting.
// You could turn off shadows by commenting out the call to
// MakeRenderInstances, since those are the guys that will
// cast shadows. Modify which lights contribute by changing
// the criteria in IFindLightsRecur.
hsBool plLightMapGen::Open(Interface* ip, TimeValue t, bool forceRegen)
{
if( !fInterface && ip )
{
fInterface = ip;
fTime = t;
fRP = TRACKED_NEW RendParams;
fRP->SetRenderElementMgr(fInterface->GetRenderElementMgr(RS_Production));
#ifdef MF_NEW_RGC
RegisterNotification(
getRGC,
this,
NOTIFY_PRE_RENDERFRAME
);
fRenderer = (Renderer*)CreateInstance(RENDERER_CLASS_ID, Class_ID(SREND_CLASS_ID,0));
ViewParams vp;
vp.prevAffineTM = Matrix3(true);
vp.affineTM = Matrix3(true);
vp.projType = PROJ_PERSPECTIVE;
vp.hither = 1.f;
vp.yon = 30.f;
vp.distance = 1.f;
vp.zoom = 1.f;
vp.fov = hsScalarPI / 4.f;
vp.nearRange = 1.f;
vp.farRange = 30.f;
fRenderer->Open(fInterface->GetRootNode(),
nil,
&vp,
*fRP,
fInterface->GetMAXHWnd());
FrameRendParams frp;
frp.ambient.Black();
frp.background.Black();
frp.globalLightLevel.Black();
frp.frameDuration = 1.f;
frp.relSubFrameDuration = 1.f;
frp.regxmin = 0;
frp.regxmax = 1;
frp.regymin = 0;
frp.regymax = 1;
frp.blowupCenter = Point2(0.5f,0.5f);
frp.blowupFactor = Point2(1.f, 1.f);
BitmapInfo bminfo;
bminfo.SetType(BMM_TRUE_32);
bminfo.SetWidth(2);
bminfo.SetHeight(2);
bminfo.SetCustWidth(1);
bminfo.SetCustHeight(1);
if( !bminfo.Validate() )
{
// oops!
return false;
}
Bitmap* tobm = TheManager->Create(&bminfo);
fRenderer->Render(fTime,
tobm,
frp,
fInterface->GetMAXHWnd()
);
tobm->DeleteThis();
#else MF_NEW_RGC
fRGC = TRACKED_NEW plRenderGlobalContext(fInterface, fTime);
fRGC->MakeRenderInstances((plMaxNode*)fInterface->GetRootNode(), fTime);
#endif // MF_NEW_RGC
fPreppedMipmaps.SetCount(0);
fCreatedLayers.SetCount(0);
fNewMaps.SetCount(0);
fAllLights.SetCount(0);
fActiveLights.SetCount(0);
IFindLightsRecur((plMaxNode*)fInterface->GetRootNode());
}
fRecalcLightMaps = forceRegen;
return fAllLights.GetCount() > 0;
}
hsBool plLightMapGen::Close()
{
// HACK to get rid of keys held by the lightmap components, because
// we can't delete the bitmaps in ICompressLightMaps unless these
// refs are gone
for (int i = 0; i < fSharedComponents.size(); i++)
{
if (fSharedComponents[i]->GetLightMapKey()) // if it has a key
fSharedComponents[i]->SetLightMapKey(nil); // nil it out
}
fSharedComponents.clear();
ICompressLightMaps();
#ifndef MF_NEW_RGC
delete fRGC;
#else // MF_NEW_RGC
if( fRenderer )
fRenderer->Close(fInterface->GetMAXHWnd());
fRenderer = nil;
#endif // MF_NEW_RGC
fRGC = nil;
delete fRP;
fRP = nil;
fPreppedMipmaps.SetCount(0);
fCreatedLayers.SetCount(0);
fNewMaps.SetCount(0);
IReleaseActiveLights();
IReleaseAllLights();
fInterface = nil;
return true;
}
//#define MIPMAP_LOG
#ifdef MIPMAP_LOG
void DumpMipmap(plMipmap* mipmap, const char* prefix)
{
hsUNIXStream dump;
char buf[256];
sprintf(buf, "log\\%s.txt", prefix);
dump.Open(buf, "wt");
for (int i = 0; i < mipmap->GetNumLevels(); i++)
{
mipmap->SetCurrLevel(i);
UInt32 width = mipmap->GetCurrWidth();
UInt32 height = mipmap->GetCurrHeight();
sprintf(buf, "----- Level %d (%dx%d) -----\n", i, width, height);
dump.WriteString(buf);
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
UInt32 color = *(mipmap->GetAddr32(x, y));
UInt8 r = ((UInt8)((color)>>16));
UInt8 g = ((UInt8)((color)>>8));
UInt8 b = ((UInt8)((color)>>0));
UInt8 a = ((UInt8)((color)>>24));
sprintf(buf, "[%3d,%3d,%3d,%3d]", r, g, b, a);
dump.WriteString(buf);
}
dump.WriteString("\n");
}
}
dump.Close();
}
#endif // MIPMAP_LOG
hsBool plLightMapGen::ICompressLightMaps()
{
int i;
for( i = 0; i < fPreppedMipmaps.GetCount(); i++ )
{
plMipmap* orig = fPreppedMipmaps[i];
if( orig )
{
const hsScalar kFilterSigma = 1.0f;
if( IsFresh(orig) )
{
hsAssert(!orig->IsCompressed(), "How did we just generate a compressed texture?");
orig->Filter(kFilterSigma);
}
if( !orig->IsCompressed() && !(orig->GetFlags() & plMipmap::kForceNonCompressed) )
{
#ifdef MIPMAP_LOG
DumpMipmap(orig, orig->GetKeyName());
#endif // MIPMAP_LOG
plMipmap *compressed =
hsCodecManager::Instance().CreateCompressedMipmap(plMipmap::kDirectXCompression, orig);
if( compressed )
{
const plLocation &textureLoc = plPluginResManager::ResMgr()->GetCommonPage(orig->GetKey()->GetUoid().GetLocation(),
plAgeDescription::kTextures );
char name[512];
sprintf(name, "%s_DX", orig->GetKey()->GetName());
plKey compKey = hsgResMgr::ResMgr()->FindKey(plUoid(textureLoc, plMipmap::Index(), name));
if( compKey )
plBitmapCreator::Instance().DeleteExportedBitmap(compKey);
hsgResMgr::ResMgr()->NewKey( name, compressed, textureLoc );
int j;
for( j = 0; j < fCreatedLayers.GetCount(); j++ )
{
if( orig == fCreatedLayers[j]->GetTexture() )
{
fCreatedLayers[j]->GetKey()->Release(orig->GetKey());
hsgResMgr::ResMgr()->AddViaNotify(compressed->GetKey(), TRACKED_NEW plLayRefMsg(fCreatedLayers[j]->GetKey(), plRefMsg::kOnReplace, 0, plLayRefMsg::kTexture), plRefFlags::kActiveRef);
}
}
plBitmapCreator::Instance().DeleteExportedBitmap(orig->GetKey());
}
}
}
}
return true;
}
hsBool plLightMapGen::MakeMaps(plMaxNode* node, const hsMatrix44& l2w, const hsMatrix44& w2l, hsTArray<plGeometrySpan *> &spans, plErrorMsg *pErrMsg, plConvertSettings *settings)
{
const char* dbgNodeName = node->GetName();
plLightMapComponent* lmapComp = node->GetLightMapComponent();
if( !lmapComp )
return false;
SetUVWSrc(lmapComp->GetUVWSrc());
SetScale(lmapComp->GetScale());
// If we don't want maps here, don't bother.
if( !IWantsMaps(node) )
{
pErrMsg->Set(true, node->GetName(), "Lightmap generation requested on bogus object").CheckAndAsk();
return false;
}
if( !IValidateUVWSrc(spans) )
{
pErrMsg->Set(true, node->GetName(), "Lightmap generation requested but UVW src bogus. Check mapping.").CheckAndAsk();
return false;
}
// If there aren't any lights, don't bother
if( !InitNode(node, false) )
{
pErrMsg->Set(true, node->GetName(), "Lightmap generation requested but no lights on object. Kind of wasteful.").CheckAndAsk();
return true;
}
// If we have trouble getting a bitmap size, there's probably something wrong with the geometry
if( !ISelectBitmapDimension(node, l2w, w2l, spans) )
{
pErrMsg->Set(true, node->GetName(), "Lightmap generation failure determining bitmap size, probably geometry problem.").CheckAndAsk();
return false;
}
// Okay, we're going to do it. The lights are
// set up for this guy so we just need some geometry.
// Find the drawable and which spans correspond
// to this node, and feed them through.
//
// IShadeGeometrySpans() and lower return whether any light was actually found.
if( !IShadeGeometrySpans(node, l2w, w2l, spans) )
{
pErrMsg->Set(true, node->GetName(), "Lightmap generation requested but no light found on object. Kind of wasteful.").CheckAndAsk();
}
DeInitNode();
return true;
}
// The next couple of functions don't do anything interesting except
// get us down to the face level where we can work.
hsBool plLightMapGen::IShadeGeometrySpans(plMaxNode* node, const hsMatrix44& l2w, const hsMatrix44& w2l, hsTArray<plGeometrySpan *> &spans)
{
hsBool retVal = false;
int i;
for( i = 0; i < spans.GetCount(); i++ )
{
retVal |= IShadeSpan(node, l2w, w2l, *spans[i]);
}
return retVal;
}
hsBool plLightMapGen::IsFresh(plBitmap* map) const
{
return fRecalcLightMaps || fNewMaps.Find(map) != fNewMaps.kMissingIndex;
}
hsBool plLightMapGen::IShadeSpan(plMaxNode* node, const hsMatrix44& l2w, const hsMatrix44& w2l, plGeometrySpan& span)
{
// This will look for a suitable lightmap layer and return that. If there
// isn't one already, it will set one up for us.
plLayerInterface* lay = IGetLightMapLayer(node, span);
// This next check should never happen, since we've created the layer ourselves.
if( !lay || !lay->GetTexture() )//|| !lay->GetTexture()->GetBitmap() )
return false;
int i;
if( !(span.fProps & plGeometrySpan::kDiffuseFoldedIn) )
{
hsBool foldin = 0 != (span.fProps & plGeometrySpan::kLiteVtxNonPreshaded);
hsScalar opacity = 1.f;
hsColorRGBA dif = hsColorRGBA().Set(1.f, 1.f, 1.f, 1.f);
if( foldin )
{
// Find the opacity to fold in.
// There should be one (or less) layers in this material using layer opacity,
// or else we should have put this span as kLiteMaterial instead of kLiteVtxNonPreshaded,
// so we're safe just getting the first opacity on a blended layer.
// Likewise we're safe getting the first diffuse color that's not on an
// emissive layer (since emissive layers ignore lightmapping).
// If we are using kLiteMaterial, we still need to copy from InitColor to Stuff,
// just don't do the modulate.
for( i = 0; i < span.fMaterial->GetNumLayers(); i++ )
{
if( span.fMaterial->GetLayer(i)->GetBlendFlags() & hsGMatState::kBlendAlpha )
{
opacity = span.fMaterial->GetLayer(i)->GetOpacity();
break;
}
}
for( i = 0; i < span.fMaterial->GetNumLayers(); i++ )
{
if( !(span.fMaterial->GetLayer(i)->GetShadeFlags() & hsGMatState::kShadeEmissive) )
{
dif = span.fMaterial->GetLayer(i)->GetRuntimeColor();
break;
}
}
}
for( i = 0; i < span.fNumVerts; i++ )
{
hsColorRGBA multColor, addColor;
span.ExtractInitColor( i, &multColor, &addColor);
if( foldin )
{
multColor *= dif; // We like to use kVertexNonPreshaded for lightmapped objects, which needs the runtime diffuse folded in
multColor.a *= opacity;
}
addColor.Set(0,0,0,0);
span.StuffVertex(i, &multColor, &addColor);
}
if( span.fInstanceRefs )
{
int j;
for( j = 0; j < span.fInstanceRefs->GetCount(); j++ )
{
plGeometrySpan* inst = (*span.fInstanceRefs)[j];
inst->fProps |= plGeometrySpan::kDiffuseFoldedIn;
}
}
}
else
{
for( i = 0; i < span.fNumVerts; i++ )
{
hsColorRGBA multColor, addColor;
span.ExtractInitColor( i, &multColor, &addColor);
addColor.Set(0,0,0,0);
span.StuffVertex(i, &multColor, &addColor);
}
return true;
}
// If we aren't recalculating all our lightmaps, then we only want to compute lightmaps
// which have a creation time of now.
if( !IsFresh(lay->GetTexture()) )
return true;
plMipmap* accum = IMakeAccumBitmap(lay);
hsBool retVal = false;
int nFaces = span.fNumIndices / 3;
for( i = 0; i < nFaces; i++ )
{
retVal |= IShadeFace(node, l2w, w2l, span, i, accum);
}
IAddToLightMap(lay, accum);
return retVal;
}
plMipmap* plLightMapGen::IMakeAccumBitmap(plLayerInterface* lay) const
{
plMipmap* dst = plMipmap::ConvertNoRef( lay->GetTexture() );//->GetBitmap();
hsAssert( dst != nil, "nil mipmap in IMakeAccumBitmap()" );
int width = dst->GetWidth();
int height = dst->GetHeight();
// Temporary mipmap here, so we don't have to worry about using plBitmapCreator
plMipmap* bitmap = TRACKED_NEW plMipmap( width, height, plMipmap::kRGB32Config, 1 );
HSMemory::Clear(bitmap->GetImage(), bitmap->GetHeight() * bitmap->GetRowBytes() );
return bitmap;
}
hsBool plLightMapGen::IAddToLightMap(plLayerInterface* lay, plMipmap* src) const
{
plMipmap* dst = plMipmap::ConvertNoRef( lay->GetTexture() );//->GetBitmap();
hsAssert( dst != nil, "nil mipmap in IAddToLightMap()" );
src->SetCurrLevel( 0 );
dst->SetCurrLevel( 0 );
// BLURLATER
// static hsScalar kFilterSigma = 0.5f;
// src->Filter(kFilterSigma);
// What we really want to do here is antialias our rasterization, so we can
// just sum in contributions of lighting at the boarder between spans sharing
// a light map. A quick hackaround is to use the max between existing color
// (in dst) and current spans illumination contribution (in src).
int i, j;
for( j = 0; j < dst->GetHeight(); j++ )
{
for( i = 0; i < dst->GetWidth(); i++ )
{
UInt32 srcRed = (*src->GetAddr32(i, j) >> 16) & 0xff;
UInt32 dstRed = (*dst->GetAddr32(i, j) >> 16) & 0xff;
// dstRed += srcRed;
if( dstRed < srcRed )
dstRed = srcRed;
if( dstRed > 0xff )
dstRed = 0xff;
UInt32 srcGreen = (*src->GetAddr32(i, j) >> 8) & 0xff;
UInt32 dstGreen = (*dst->GetAddr32(i, j) >> 8) & 0xff;
// dstGreen += srcGreen;
if( dstGreen < srcGreen )
dstGreen = srcGreen;
if( dstGreen > 0xff )
dstGreen = 0xff;
UInt32 srcBlue = (*src->GetAddr32(i, j) >> 0) & 0xff;
UInt32 dstBlue = (*dst->GetAddr32(i, j) >> 0) & 0xff;
// dstBlue += srcBlue;
if( dstBlue < srcBlue )
dstBlue = srcBlue;
if( dstBlue > 0xff )
dstBlue = 0xff;
*dst->GetAddr32(i, j) = 0xff000000
| (dstRed << 16)
| (dstGreen << 8)
| (dstBlue << 0);
}
}
dst->MakeDirty();
delete src;
return true;
}
hsBool plLightMapGen::IShadeFace(plMaxNode* node, const hsMatrix44& l2w, const hsMatrix44& w2l, plGeometrySpan& span, int iFace, plMipmap* bitmap)
{
// Okay, here's where the metal hits the road, whatever that means.
// We're going to get our bitmap, and step along the face texel by texel,
// summing up the light at each texel and stuffing it in the bitmap.
// Set up a light context for the shading below.
Box3 bbox;
node->EvalWorldState(fTime).obj->GetDeformBBox(fTime, bbox, &node->GetObjectTM(fTime));
plMaxLightContext ctx(bbox, fTime);
// First, get the face info we'll be using.
// This will look for a suitable lightmap layer and return that.
// There should be one there already, because we called this
// in IShadeSpan
plLayerInterface* lay = IGetLightMapLayer(node, span);
int iOurUv = IGetUVWSrc();
// A little late to be checking this, but whatever...
if( iOurUv < 0 )
return false;
int width = bitmap->GetWidth();
int height = bitmap->GetHeight();
hsMatrix44 norml2w;
hsMatrix44 temp;
l2w.GetInverse( &temp);
temp.GetTranspose( &norml2w );
hsPoint3 pt[3];
hsVector3 norm[3];
hsPoint3 uv[3];
int i;
for( i = 0; i < 3; i++ )
{
hsColorRGBA trash;
span.ExtractVertex(span.fIndexData[iFace*3 + i], &pt[i], &norm[i], &trash);
span.ExtractUv(span.fIndexData[iFace*3 + i], iOurUv, &uv[i]);
pt[i] = l2w * pt[i];
norm[i] = norml2w * norm[i];
uv[i] = lay->GetTransform() * uv[i];
uv[i].fX *= width-1;
uv[i].fX += 0.5f;
uv[i].fY *= height-1;
uv[i].fY += 0.5f;
}
Color amb(0,0,0);
return IShadeVerts(ctx, amb, pt, norm, uv, bitmap);
}
hsBool plLightMapGen::IShadeVerts(plMaxLightContext& ctx, const Color& amb, const hsPoint3 pt[3], const hsVector3 norm[3], const hsPoint3 uv[3], plMipmap* bitmap)
{
int width = bitmap->GetWidth();
int height = bitmap->GetHeight();
bitmap->SetCurrLevel( 0 );
hsTArray<LMGScanlineData> scanline;
scanline.SetCount(height);
int lowestV = height;
int highestV = 0;
int i0, i1, i2;
for( i0 = 0; i0 < 3; i0++ )
{
i1 = i0 == 2 ? 0 : i0+1;
i2 = i1 == 2 ? 0 : i1+1;
hsScalar v0 = uv[i0].fY;
hsScalar v1 = uv[i1].fY;
int vStart = int(v0);
int vEnd = int(v1);
if( vStart == vEnd )
continue;
int vStep = vStart < vEnd ? 1 : -1;
int vMid;
for( vMid = vStart; vMid != vEnd + vStep; vMid += vStep )
{
// This shouldn't really happen, but might with some slop.
if( (vMid < 0) || (vMid >= height) )
continue;
hsPoint3 bary;
bary[i0] = (v1 - float(vMid)) / (v1 - v0);
bary[i1] = 1.f - bary[i0];
bary[i2] = 0;
hsScalar u = uv[i0].fX * bary[i0]
+ uv[i1].fX * bary[i1];
if( scanline[vMid].fEmpty )
{
scanline[vMid].fNear.fU = u;
scanline[vMid].fNear.fBary = bary;
scanline[vMid].fFar = scanline[vMid].fNear;
scanline[vMid].fEmpty = false;
if( vMid < lowestV )
lowestV = vMid;
if( vMid > highestV )
highestV = vMid;
}
else
{
if( u < scanline[vMid].fNear.fU )
{
scanline[vMid].fNear.fU = u;
scanline[vMid].fNear.fBary = bary;
}
else if( u > scanline[vMid].fFar.fU )
{
scanline[vMid].fFar.fU = u;
scanline[vMid].fFar.fBary = bary;
}
}
}
}
int i;
for( i = lowestV; i <= highestV; i++ )
{
if( !scanline[i].fEmpty )
{
int uStart = int(scanline[i].fNear.fU);
if( uStart < 0 )
uStart = 0;
int uEnd = int(scanline[i].fFar.fU);
if( uEnd >= width )
uEnd = width - 1;
if( uStart == uEnd )
continue;
int uMid;
for( uMid = uStart; uMid <= uEnd; uMid++ )
{
hsScalar t = (scanline[i].fFar.fU - float(uMid)) / (scanline[i].fFar.fU - scanline[i].fNear.fU);
hsPoint3 bary = scanline[i].fNear.fBary * t;
bary += scanline[i].fFar.fBary * (1.f - t);
hsPoint3 p = pt[0] * bary[0] + pt[1] * bary[1] + pt[2] * bary[2];
hsVector3 n = norm[0] * bary[0] + norm[1] * bary[1] + norm[2] * bary[2];
hsFastMath::NormalizeAppr(n);
UInt32 color = IShadePoint(ctx, amb, p, n);
*bitmap->GetAddr32(uMid, i) = color;
}
}
}
return true;
}
hsBool plLightMapGen::IGetLight(INode* node)
{
if( node->UserPropExists("RunTimeLight") )
return false;
Object *obj = node->EvalWorldState(fTime).obj;
if (obj && (obj->SuperClassID() == SClass_ID(LIGHT_CLASS_ID)))
{
plLightMapInfo* liInfo = fAllLights.Push();
LightObject* liObj = (LightObject*)obj;
liInfo->fResetShadowType = 0;
liInfo->fResetMapRange = -1.f;
liInfo->fMapRange = -1.f;
liInfo->fLiNode = node;
liInfo->fObjLiDesc = nil;
liInfo->fNewRender = true;
return true;
}
return false;
}
hsBool plLightMapGen::Update(TimeValue t)
{
fTime = t;
#ifndef MF_NEW_RGC
if( fRGC )
fRGC->Update(t);
#endif // MF_NEW_RGC
return fAllLights.GetCount() != 0;
}
hsBool plLightMapGen::IFindLightsRecur(INode* node)
{
IGetLight(node);
int i;
for( i = 0; i < node->NumberOfChildren(); i++ )
IFindLightsRecur(node->GetChildNode(i));
return fAllLights.GetCount() > 0;
}
hsBool plLightMapGen::InitNode(INode* node, hsBool softShadow)
{
fActiveLights.SetCount(0);
plMaxNode* maxNode = (plMaxNode*)node;
if( !maxNode->CanConvert() )
return false;
if( maxNode->GetNoPreShade() )
return false;
#ifndef MF_NO_SHADOW_BLUR
fMapRange = softShadow ? kBlurMapRange : -1.f;
#endif // MF_NO_SHADOW_BLUR
IFindActiveLights((plMaxNode*)node);
return fActiveLights.GetCount() > 0;
}
hsBool plLightMapGen::DeInitNode()
{
IReleaseActiveLights();
return true;
}
hsBounds3Ext plLightMapGen::IGetBoundsLightSpace(INode* node, INode* liNode)
{
TimeValue currTime(0);
hsBounds3Ext bnd;
bnd.MakeEmpty();
Object *obj = node->EvalWorldState(currTime).obj;
if( !obj )
return bnd;
Box3 box;
if( obj->ClassID() == Class_ID(DUMMY_CLASS_ID,0) )
{
DummyObject* dummy = (DummyObject*)obj;
box = dummy->GetBox();
}
else
if( obj->CanConvertToType(triObjectClassID) )
{
TriObject *meshObj = (TriObject *)obj->ConvertToType(currTime, triObjectClassID);
if( !meshObj )
return bnd;
Mesh& mesh = meshObj->mesh;
box = mesh.getBoundingBox();
if( meshObj != obj )
meshObj->DeleteThis();
}
bnd.Union(&hsPoint3(box.pmin.x, box.pmin.y, box.pmin.z));
bnd.Union(&hsPoint3(box.pmax.x, box.pmax.y, box.pmax.z));
Matrix3 maxL2W = node->GetObjectTM(currTime);
Matrix3 maxW2Light = Inverse(liNode->GetObjectTM(currTime));
Matrix3 maxL2Light = maxL2W * maxW2Light;
hsMatrix44 l2l;
hsControlConverter::Instance().Matrix3ToHsMatrix44(&maxL2Light, &l2l);
bnd.Transform(&l2l);
return bnd;
}
hsBool plLightMapGen::IDirAffectsNode(plLightMapInfo* liInfo, LightObject* liObj, INode* node)
{
hsBounds3Ext bnd = IGetBoundsLightSpace(node, liInfo->fLiNode);
if( bnd.GetType() != kBoundsNormal )
return false;
if( bnd.GetMins().fZ > 0 )
return false;
LightState ls;
liObj->EvalLightState(TimeValue(0), FOREVER, &ls);
hsScalar radX = ls.fallsize;
hsScalar radY = radX;
if( ls.shape == RECT_LIGHT )
radY /= ls.aspect;
if( bnd.GetMins().fX > radX )
return false;
if( bnd.GetMaxs().fX < -radX )
return false;
if( bnd.GetMins().fY > radY )
return false;
if( bnd.GetMaxs().fY < -radY )
return false;
if( !ls.useAtten )
return true;
if( bnd.GetMaxs().fZ < -ls.attenEnd )
return false;
return true;
}
hsBool plLightMapGen::ISpotAffectsNode(plLightMapInfo* liInfo, LightObject* liObj, INode* node)
{
hsBounds3Ext bnd = IGetBoundsLightSpace(node, liInfo->fLiNode);
if( bnd.GetType() != kBoundsNormal )
return false;
if( bnd.GetMins().fZ > 0 )
return false;
LightState ls;
liObj->EvalLightState(TimeValue(0), FOREVER, &ls);
hsScalar coneRad[2];
coneRad[0] = ls.fallsize * hsScalarPI / 180.f;
coneRad[1] = coneRad[0];
if( ls.shape == RECT_LIGHT )
coneRad[1] /= ls.aspect;
hsPoint3 corners[8];
bnd.GetCorners(corners);
int numPos[4] = { 0, 0, 0, 0 };
int j;
for( j = 0; j < 8; j++ )
{
hsScalar rad;
rad = hsScalar(atan2(corners[j].fX, -corners[j].fZ));
if( rad > coneRad[0] )
numPos[0]++;
if( rad < -coneRad[0] )
numPos[2]++;
rad = hsScalar(atan2(corners[j].fY, -corners[j].fZ));
if( rad > coneRad[1] )
numPos[1]++;
if( rad < -coneRad[1] )
numPos[3]++;
}
for( j = 0; j < 4; j++ )
{
if( numPos[j] >= 8 )
return false;
}
if( ls.useAtten )
{
if( bnd.GetMaxs().fZ < -ls.attenEnd )
return false;
}
return true;
}
hsBool plLightMapGen::IOmniAffectsNode(plLightMapInfo* liInfo, LightObject* liObj, INode* node)
{
LightState ls;
liObj->EvalLightState(TimeValue(0), FOREVER, &ls);
if( !ls.useAtten )
return true;
hsBounds3Ext bnd = IGetBoundsLightSpace(node, liInfo->fLiNode);
if( bnd.GetType() != kBoundsNormal )
return false;
hsScalar radius = ls.attenEnd;
int i;
for( i = 0; i < 3; i++ )
{
if( bnd.GetMins()[i] > radius )
return false;
if( bnd.GetMaxs()[i] < -radius )
return false;
}
return true;
}
hsBool plLightMapGen::ILightAffectsNode(plLightMapInfo* liInfo, LightObject* liObj, INode* node)
{
const char* liName = liInfo->fLiNode->GetName();
const char* nodeName = node->GetName();
LightState ls;
liObj->EvalLightState(TimeValue(0), FOREVER, &ls);
hsBool excluded = false;
if( !liObj->GetUseLight() )
{
excluded = true;
}
if( !excluded && liObj->GetExclList() && liObj->GetExclList()->TestFlag(NT_AFFECT_ILLUM) )
{
hsBool inExc = -1 != liObj->GetExclList()->FindNode(node);
if( (!inExc) ^ (!liObj->GetExclList()->TestFlag(NT_INCLUDE)) )
excluded = true;
}
if( excluded )
return false;
switch( ls.type )
{
case OMNI_LGT:
return IOmniAffectsNode(liInfo, liObj, node);
case SPOT_LGT:
return ISpotAffectsNode(liInfo, liObj, node);
case DIRECT_LGT:
return IDirAffectsNode(liInfo, liObj, node);
default:
case AMBIENT_LGT:
return true;
}
return false;
}
hsBool plLightMapGen::IPrepLight(plLightMapInfo* liInfo, INode* node)
{
const char* liName = liInfo->fLiNode->GetName();
const char* nodeName = node->GetName();
INode* liNode = liInfo->fLiNode;
LightObject* liObj = (LightObject*)liNode->EvalWorldState(fTime).obj;
// redundant check, if it doesn't have a light object it shouldn't be in the list
if( liObj )
{
hsBool affectsNode = ILightAffectsNode(liInfo, liObj, node);
if( affectsNode )
{
#ifdef MF_NO_RAY_SHADOW
// for reasons known only to god and someone deep in the bowels of kinetix,
// the lighting is (sometimes) barfing if the shadow type is ray-traced.
// until i can track that down, i'll force shadow mapped shadows.
liInfo->fResetShadowType = liObj->GetShadowType();
if( liInfo->fResetShadowType > 0 )
{
liObj->SetShadowType(0);
liInfo->fNewRender = true;
}
#endif MF_NO_RAY_SHADOW
if( fMapRange > 0 )
{
if( liInfo->fMapRange != fMapRange )
{
liInfo->fResetMapRange = liObj->GetMapRange(fTime);
liObj->SetMapRange(fTime, fMapRange);
liInfo->fMapRange = fMapRange;
liInfo->fNewRender = true;
}
}
else if( liInfo->fResetMapRange > 0 )
{
if( liInfo->fMapRange != liInfo->fResetMapRange )
{
liObj->SetMapRange(fTime, liInfo->fResetMapRange);
liInfo->fMapRange = liInfo->fResetMapRange;
liInfo->fNewRender = true;
}
}
ObjLightDesc* objLiDesc = liInfo->fObjLiDesc;
if( !objLiDesc )
objLiDesc = liObj->CreateLightDesc(liNode);
plMaxRendContext rc;
objLiDesc->Update(fTime, rc, fRGC, node->RcvShadows(), liInfo->fNewRender);
objLiDesc->UpdateViewDepParams(Matrix3(true));
liInfo->fNewRender = false;
liInfo->fObjLiDesc = objLiDesc;
fActiveLights.Append(liInfo);
}
}
return true;
}
hsBool plLightMapGen::IFindActiveLights(plMaxNode* node)
{
fActiveLights.SetCount(0);
int i;
for( i = 0; i < fAllLights.GetCount(); i++ )
{
IPrepLight(&fAllLights[i], node);
}
return fActiveLights.GetCount() > 0;
}
hsBool plLightMapGen::IReleaseAllLights()
{
int i;
for( i = 0; i < fAllLights.GetCount(); i++ )
{
if( fAllLights[i].fResetMapRange > 0 )
{
LightObject* liObj = (LightObject*)fAllLights[i].fLiNode->EvalWorldState(fTime).obj;
liObj->SetMapRange(fTime, fAllLights[i].fResetMapRange);
}
#ifdef MF_NO_RAY_SHADOW
// Fix the shadow method back.
if( fAllLights[i].fResetShadowType > 0 )
{
LightObject* liObj = (LightObject*)fAllLights[i].fLiNode->EvalWorldState(fTime).obj;
liObj->SetShadowType(fAllLights[i].fResetShadowType);
}
#endif // MF_NO_RAY_SHADOW
if( fAllLights[i].fObjLiDesc )
fAllLights[i].fObjLiDesc->DeleteThis();
fAllLights[i].fObjLiDesc = nil;
}
fAllLights.SetCount(0);
return true;
}
hsBool plLightMapGen::IReleaseActiveLights()
{
fActiveLights.SetCount(0);
return true;
}
hsBool plLightMapGen::IWantsMaps(plMaxNode* node)
{
if( !(node->CanConvert() && node->GetDrawable()) )
return false;
return nil != node->GetLightMapComponent();
}
hsBool plLightMapGen::IValidateUVWSrc(hsTArray<plGeometrySpan *>& spans) const
{
int i;
for( i = 0; i < spans.GetCount(); i++ )
{
int numUVWs = spans[i]->GetNumUVs();
if( IGetUVWSrc() >= numUVWs )
return false;
}
return true;
}
void plLightMapGen::IInitBitmapColor(plMipmap* bitmap, const hsColorRGBA& col) const
{
UInt32 initColor = MakeUInt32Color(col.r, col.g, col.b, col.a);
UInt32* pix = (UInt32*)bitmap->GetImage();
UInt32* pixEnd = ((UInt32*)bitmap->GetImage()) + bitmap->GetWidth() * bitmap->GetHeight();
while( pix < pixEnd )
*pix++ = initColor;
}
plLayerInterface* plLightMapGen::IGetLightMapLayer(plMaxNode* node, plGeometrySpan& span)
{
plLayerInterface* lay = IMakeLightMapLayer(node, span);
plMipmap* mip = plMipmap::ConvertNoRef(lay->GetTexture());
hsAssert(mip, "This should have been a mipmap we created ourselves.");
if( !mip )
return nil;
if( fPreppedMipmaps.Find(mip) == fPreppedMipmaps.kMissingIndex )
{
if( IsFresh(mip) )
{
hsColorRGBA initColor = node->GetLightMapComponent()->GetInitColor();
// Get this off the node, where the lightmap component has stashed it.
IInitBitmapColor(mip, initColor);
}
fPreppedMipmaps.Append(mip);
}
if( fCreatedLayers.Find(lay) == fCreatedLayers.kMissingIndex )
{
fCreatedLayers.Append(lay);
}
return lay;
}
plLayerInterface* plLightMapGen::IMakeLightMapLayer(plMaxNode* node, plGeometrySpan& span)
{
hsGMaterial* mat = span.fMaterial;
int i;
for( i = 0; i < mat->GetNumPiggyBacks(); i++ )
{
if( mat->GetPiggyBack(i)->GetMiscFlags() & hsGMatState::kMiscLightMap )
return mat->GetPiggyBack(i);
}
char newMatName[256];
sprintf(newMatName, "%s_%s_LIGHTMAPGEN", mat->GetKey()->GetName(), node->GetName());
plLocation nodeLoc = node->GetLocation();
plKey matKey = hsgResMgr::ResMgr()->FindKey(plUoid(nodeLoc, hsGMaterial::Index(), newMatName));
if( matKey )
{
mat = hsGMaterial::ConvertNoRef(matKey->ObjectIsLoaded());
for( i = 0; i < mat->GetNumPiggyBacks(); i++ )
{
if( mat->GetPiggyBack(i)->GetMiscFlags() & hsGMatState::kMiscLightMap )
{
span.fMaterial = mat;
return mat->GetPiggyBack(i);
}
}
hsAssert(false, "Something not a light map material registered with our name?");
}
hsGMaterial* objMat = nil;
hsBool sharemaps = node->GetLightMapComponent()->GetShared();
if( sharemaps )
{
objMat = mat;
}
else
{
objMat = TRACKED_NEW hsGMaterial;
hsgResMgr::ResMgr()->NewKey(newMatName, objMat, nodeLoc);
for( i = 0; i < mat->GetNumLayers(); i++ )
hsgResMgr::ResMgr()->AddViaNotify(mat->GetLayer(i)->GetKey(), TRACKED_NEW plMatRefMsg(objMat->GetKey(), plRefMsg::kOnCreate, -1, plMatRefMsg::kLayer), plRefFlags::kActiveRef);
}
objMat->SetCompositeFlags(objMat->GetCompositeFlags() | hsGMaterial::kCompIsLightMapped);
// Make sure layer (and mip) name are unique across pages by putting the page name in
const plPageInfo* pageInfo = plKeyFinder::Instance().GetLocationInfo(node->GetLocation());
char layName[256];
sprintf(layName, "%s_%s_LIGHTMAPGEN", pageInfo->GetPage(), node->GetName());
plKey layKey = node->FindPageKey(plLayer::Index(), layName);
if( !layKey )
{
int w = fWidth;
int h = fHeight;
plKey mipKey;
if( node->GetLightMapComponent()->GetLightMapKey() )
{
mipKey = node->GetLightMapComponent()->GetLightMapKey();
}
else
{
char mipmapName[ 256 ];
sprintf( mipmapName, "%s_mip", layName );
// Deleted the NOTE here because it was incorrect in every meaningful sense of the word. - mf
const plLocation &textureLoc = plPluginResManager::ResMgr()->GetCommonPage( nodeLoc, plAgeDescription::kTextures );
mipKey = hsgResMgr::ResMgr()->FindKey(plUoid(textureLoc, plMipmap::Index(), mipmapName));
if( !mipKey && !fRecalcLightMaps )
{
char compressedName[512];
sprintf(compressedName, "%s_DX", mipmapName);
plKey compKey = hsgResMgr::ResMgr()->FindKey(plUoid(textureLoc, plMipmap::Index(), compressedName));
if( compKey )
mipKey = compKey;
}
if( mipKey )
{
plBitmap* bitmap = plBitmap::ConvertNoRef(mipKey->ObjectIsLoaded());
if( bitmap )
{
if( node->GetLightMapComponent()->GetCompress() != bitmap->IsCompressed() )
{
// make sure the lightmap component isn't holding a key,
// it will get assigned one a few lines later anyway
if (node->GetLightMapComponent()->GetLightMapKey())
node->GetLightMapComponent()->SetLightMapKey(nil);
plBitmapCreator::Instance().DeleteExportedBitmap(mipKey);
}
}
}
if( !mipKey )
{
plMipmap* bitmap = plBitmapCreator::Instance().CreateBlankMipmap(w, h, plMipmap::kRGB32Config, 1, mipmapName, nodeLoc);
mipKey = bitmap->GetKey();
fNewMaps.Append(bitmap);
if( !node->GetLightMapComponent()->GetCompress() )
bitmap->SetFlags(bitmap->GetFlags() | plMipmap::kForceNonCompressed);
}
if( node->GetLightMapComponent()->GetShared() )
{
// HACK since we are setting the key, save the pointer to the light map
// component so we can get rid of the key it holds later
fSharedComponents.push_back(node->GetLightMapComponent());
node->GetLightMapComponent()->SetLightMapKey(mipKey);
}
}
plLayer* layer = TRACKED_NEW plLayer;
layer->InitToDefault();
layKey = hsgResMgr::ResMgr()->NewKey(layName, layer, nodeLoc);
hsgResMgr::ResMgr()->AddViaNotify(mipKey, TRACKED_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);
layer->SetBlendFlags(hsGMatState::kBlendMult);
layer->SetClampFlags(hsGMatState::kClampTexture);
layer->SetUVWSrc(IGetUVWSrc());
layer->SetMiscFlags(hsGMatState::kMiscLightMap);
}
hsgResMgr::ResMgr()->AddViaNotify(layKey, TRACKED_NEW plMatRefMsg(objMat->GetKey(), plRefMsg::kOnCreate, -1, plMatRefMsg::kPiggyBack), plRefFlags::kActiveRef);
span.fMaterial = objMat;
return plLayerInterface::ConvertNoRef(layKey->GetObjectPtr());
}
// Like ShadePoint, but only computes the amount of light striking the surface,
// so ignoring the N dot L term.
Color plLightMapGen::ShadowPoint(plMaxLightContext& ctx)
{
ctx.globContext = fRGC;
Color accum;
accum.Black();
int i;
for( i = 0; i < fActiveLights.GetCount(); i++ )
{
const char* dbgLiName = fActiveLights[i]->fLiNode->GetName();
Color color;
Point3 liDir;
float dot_nl, diffuseCoef;
BOOL hit = fActiveLights[i]->fObjLiDesc->Illuminate(ctx, ctx.Normal(), color, liDir, dot_nl, diffuseCoef);
if( hit )
{
accum += color;
}
}
return accum;
}
Color plLightMapGen::ShadePoint(plMaxLightContext& ctx)
{
ctx.globContext = fRGC;
Color accum;
accum.Black();
int i;
for( i = 0; i < fActiveLights.GetCount(); i++ )
{
Color color;
Point3 liDir;
float dot_nl, diffuseCoef;
BOOL hit = fActiveLights[i]->fObjLiDesc->Illuminate(ctx, ctx.Normal(), color, liDir, dot_nl, diffuseCoef);
if( hit )
{
accum += color * diffuseCoef;
}
}
return accum;
}
Color plLightMapGen::ShadePoint(plMaxLightContext& ctx, const Point3& p, const Point3& n)
{
ctx.SetPoint(p, n);
return ShadePoint(ctx);
}
Color plLightMapGen::ShadePoint(plMaxLightContext& ctx, const hsPoint3& p, const hsVector3& n)
{
ctx.SetPoint(p, n);
return ShadePoint(ctx);
}
UInt32 plLightMapGen::IShadePoint(plMaxLightContext& ctx, const Color& amb, const hsPoint3& p, const hsVector3& n)
{
ctx.globContext = fRGC;
ctx.SetPoint(p, n);
Color accum = ShadePoint(ctx);
accum += amb;
accum.ClampMinMax();
UInt32 retVal;
retVal = MakeUInt32Color(accum.r, accum.g, accum.b, 1.f);
return retVal;
}
hsBool plLightMapGen::ISelectBitmapDimension(plMaxNode* node, const hsMatrix44& l2w, const hsMatrix44& w2l, hsTArray<plGeometrySpan *>& spans)
{
float duDr = 0;
float dvDr = 0;
float totFaces = 0;
int i;
for( i = 0; i < spans.GetCount(); i++ )
{
plGeometrySpan *span = spans[i];
int nFaces = span->fNumIndices / 3;
int j;
for( j = 0; j < nFaces; j++ )
{
hsPoint3 pt[3];
hsPoint3 uv[3];
int k;
for( k = 0; k < 3; k++ )
{
hsVector3 vTrash;
hsColorRGBA cTrash;
span->ExtractVertex(span->fIndexData[j*3 + k], &pt[k], &vTrash, &cTrash);
pt[k] = l2w * pt[k];
span->ExtractUv(span->fIndexData[j*3 + k], IGetUVWSrc(), &uv[k]);
}
if( (uv[0].fX >= 1.f)
&&(uv[1].fX >= 1.f)
&&(uv[2].fX >= 1.f) )
continue;
if( (uv[0].fY >= 1.f)
&&(uv[1].fY >= 1.f)
&&(uv[2].fY >= 1.f) )
continue;
if( (uv[0].fX <= 0)
&&(uv[1].fX <= 0)
&&(uv[2].fX <= 0) )
continue;
if( (uv[0].fY <= 0)
&&(uv[1].fY <= 0)
&&(uv[2].fY <= 0) )
continue;
float magDU[2];
magDU[0] = fabsf(uv[1].fX - uv[0].fX);
magDU[1] = fabsf(uv[2].fX - uv[0].fX);
if( magDU[0] > magDU[1] )
{
float dist = hsVector3(pt+1, pt+0).Magnitude();
if( dist > 1.e-3f )
duDr += magDU[0] / dist;
}
else
{
float dist = hsVector3(pt+2, pt+0).Magnitude();
if( dist > 1.e-3f )
duDr += magDU[1] / dist;
}
float magDV[2];
magDV[0] = fabsf(uv[1].fY - uv[0].fY);
magDV[1] = fabsf(uv[2].fY - uv[0].fY);
if( magDV[0] > magDV[1] )
{
float dist = hsVector3(pt+1, pt+0).Magnitude();
if( dist > 1.e-3f )
dvDr += magDV[0] / dist;
}
else
{
float dist = hsVector3(pt+2, pt+0).Magnitude();
if( dist > 1.e-3f )
dvDr += magDV[1] / dist;
}
totFaces++;
}
}
if( totFaces < 1.f )
return false;
duDr /= totFaces;
dvDr /= totFaces;
const int kMaxSize = 256;
const int kMinSize = 32;
const int kMaxAspect = 8;
const float kTexPerFoot = 1.f;
if( duDr > 0 )
{
fWidth = kTexPerFoot / duDr;
if( fWidth > kMaxSize )
fWidth = kMaxSize;
if( fWidth < kMinSize )
fWidth = kMinSize;
}
else
{
fWidth = kMinSize;
}
fWidth *= fScale;
fWidth = IPowerOfTwo(fWidth);
if( dvDr > 0 )
{
fHeight = kTexPerFoot / duDr;
if( fHeight > kMaxSize )
fHeight = kMaxSize;
if( fHeight < kMinSize )
fHeight = kMinSize;
}
else
{
fHeight = kMinSize;
}
fHeight *= fScale;
fHeight = IPowerOfTwo(fHeight);
if( fHeight / fWidth > kMaxAspect )
fWidth = fHeight / kMaxAspect;
if( fWidth / fHeight > kMaxAspect )
fHeight = fWidth / kMaxAspect;
if( fWidth > 512 )
fWidth = 512;
if( fHeight > 512 )
fHeight = 512;
return true;
}
int plLightMapGen::IPowerOfTwo(int sz) const
{
int i = 0;
while( (1 << i) < sz )
i++;
int p2sz = 1 << i;
if( p2sz - sz > sz - (p2sz >> 1) )
p2sz >>= 1;
return p2sz;
}