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