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