/*==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;
}