You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1629 lines
46 KiB
1629 lines
46 KiB
/*==LICENSE==* |
|
|
|
CyanWorlds.com Engine - MMOG client, server and tools |
|
Copyright (C) 2011 Cyan Worlds, Inc. |
|
|
|
This program is free software: you can redistribute it and/or modify |
|
it under the terms of the GNU General Public License as published by |
|
the Free Software Foundation, either version 3 of the License, or |
|
(at your option) any later version. |
|
|
|
This program is distributed in the hope that it will be useful, |
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
GNU General Public License for more details. |
|
|
|
You should have received a copy of the GNU General Public License |
|
along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
|
|
Additional permissions under GNU GPL version 3 section 7 |
|
|
|
If you modify this Program, or any covered work, by linking or |
|
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK, |
|
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent |
|
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK |
|
(or a modified version of those libraries), |
|
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA, |
|
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG |
|
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the |
|
licensors of this Program grant you additional |
|
permission to convey the resulting work. Corresponding Source for a |
|
non-source form of such a combination shall include the source code for |
|
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered |
|
work. |
|
|
|
You can contact Cyan Worlds, Inc. by email legal@cyan.com |
|
or by snail mail at: |
|
Cyan Worlds, Inc. |
|
14617 N Newport Hwy |
|
Mead, WA 99021 |
|
|
|
*==LICENSE==*/ |
|
|
|
#include "HeadSpin.h" |
|
#include "hsFastMath.h" |
|
#include "hsTemplates.h" |
|
#include "hsWindows.h" |
|
|
|
#include "MaxComponent/plComponent.h" |
|
|
|
#include <dummy.h> |
|
#include <notify.h> |
|
#include <vector> |
|
#pragma hdrstop |
|
|
|
#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 "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: |
|
float fU; |
|
hsPoint3 fBary; |
|
}; |
|
|
|
class LMGScanlineData |
|
{ |
|
public: |
|
LMGScanlineData() : fEmpty(true) {} |
|
|
|
bool fEmpty; |
|
LMGScanPoint fNear; |
|
LMGScanPoint fFar; |
|
}; |
|
|
|
static int kDefaultSize = 64; |
|
|
|
static uint32_t MakeUInt32Color(float r, float g, float b, float a) |
|
{ |
|
return (uint32_t(a * 255.9f) << 24) |
|
|(uint32_t(r * 255.9f) << 16) |
|
|(uint32_t(g * 255.9f) << 8) |
|
|(uint32_t(b * 255.9f) << 0); |
|
} |
|
|
|
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. |
|
bool plLightMapGen::Open(Interface* ip, TimeValue t, bool forceRegen) |
|
{ |
|
if( !fInterface && ip ) |
|
{ |
|
fInterface = ip; |
|
fTime = t; |
|
|
|
fRP = 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 = M_PI / 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 = 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; |
|
} |
|
|
|
bool 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_t width = mipmap->GetCurrWidth(); |
|
uint32_t 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_t color = *(mipmap->GetAddr32(x, y)); |
|
uint8_t r = ((uint8_t)((color)>>16)); |
|
uint8_t g = ((uint8_t)((color)>>8)); |
|
uint8_t b = ((uint8_t)((color)>>0)); |
|
uint8_t a = ((uint8_t)((color)>>24)); |
|
sprintf(buf, "[%3d,%3d,%3d,%3d]", r, g, b, a); |
|
dump.WriteString(buf); |
|
} |
|
|
|
dump.WriteString("\n"); |
|
} |
|
} |
|
|
|
dump.Close(); |
|
} |
|
#endif // MIPMAP_LOG |
|
|
|
bool plLightMapGen::ICompressLightMaps() |
|
{ |
|
int i; |
|
for( i = 0; i < fPreppedMipmaps.GetCount(); i++ ) |
|
{ |
|
plMipmap* orig = fPreppedMipmaps[i]; |
|
|
|
if( orig ) |
|
{ |
|
const float 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 ); |
|
plString name = plFormat("{}_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(), new plLayRefMsg(fCreatedLayers[j]->GetKey(), plRefMsg::kOnReplace, 0, plLayRefMsg::kTexture), plRefFlags::kActiveRef); |
|
} |
|
} |
|
|
|
plBitmapCreator::Instance().DeleteExportedBitmap(orig->GetKey()); |
|
} |
|
} |
|
} |
|
} |
|
return true; |
|
} |
|
|
|
bool 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. |
|
bool plLightMapGen::IShadeGeometrySpans(plMaxNode* node, const hsMatrix44& l2w, const hsMatrix44& w2l, hsTArray<plGeometrySpan *> &spans) |
|
{ |
|
bool retVal = false; |
|
int i; |
|
for( i = 0; i < spans.GetCount(); i++ ) |
|
{ |
|
retVal |= IShadeSpan(node, l2w, w2l, *spans[i]); |
|
} |
|
return retVal; |
|
} |
|
|
|
bool plLightMapGen::IsFresh(plBitmap* map) const |
|
{ |
|
return fRecalcLightMaps || fNewMaps.Find(map) != fNewMaps.kMissingIndex; |
|
} |
|
|
|
bool 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) ) |
|
{ |
|
bool foldin = 0 != (span.fProps & plGeometrySpan::kLiteVtxNonPreshaded); |
|
float 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); |
|
|
|
bool 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 = new plMipmap( width, height, plMipmap::kRGB32Config, 1 ); |
|
HSMemory::Clear(bitmap->GetImage(), bitmap->GetHeight() * bitmap->GetRowBytes() ); |
|
|
|
return bitmap; |
|
} |
|
|
|
bool 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 float 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_t srcRed = (*src->GetAddr32(i, j) >> 16) & 0xff; |
|
uint32_t dstRed = (*dst->GetAddr32(i, j) >> 16) & 0xff; |
|
// dstRed += srcRed; |
|
if( dstRed < srcRed ) |
|
dstRed = srcRed; |
|
if( dstRed > 0xff ) |
|
dstRed = 0xff; |
|
|
|
uint32_t srcGreen = (*src->GetAddr32(i, j) >> 8) & 0xff; |
|
uint32_t dstGreen = (*dst->GetAddr32(i, j) >> 8) & 0xff; |
|
// dstGreen += srcGreen; |
|
if( dstGreen < srcGreen ) |
|
dstGreen = srcGreen; |
|
if( dstGreen > 0xff ) |
|
dstGreen = 0xff; |
|
|
|
uint32_t srcBlue = (*src->GetAddr32(i, j) >> 0) & 0xff; |
|
uint32_t 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; |
|
} |
|
|
|
bool 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); |
|
} |
|
|
|
bool 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; |
|
|
|
float v0 = uv[i0].fY; |
|
float 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; |
|
float 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++ ) |
|
{ |
|
float 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_t color = IShadePoint(ctx, amb, p, n); |
|
*bitmap->GetAddr32(uMid, i) = color; |
|
|
|
} |
|
} |
|
} |
|
|
|
return true; |
|
} |
|
|
|
bool 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; |
|
} |
|
|
|
bool plLightMapGen::Update(TimeValue t) |
|
{ |
|
fTime = t; |
|
|
|
#ifndef MF_NEW_RGC |
|
if( fRGC ) |
|
fRGC->Update(t); |
|
#endif // MF_NEW_RGC |
|
|
|
return fAllLights.GetCount() != 0; |
|
} |
|
|
|
bool plLightMapGen::IFindLightsRecur(INode* node) |
|
{ |
|
IGetLight(node); |
|
|
|
int i; |
|
for( i = 0; i < node->NumberOfChildren(); i++ ) |
|
IFindLightsRecur(node->GetChildNode(i)); |
|
|
|
return fAllLights.GetCount() > 0; |
|
} |
|
|
|
bool plLightMapGen::InitNode(INode* node, bool 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; |
|
} |
|
|
|
bool 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; |
|
} |
|
|
|
bool 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); |
|
|
|
float radX = ls.fallsize; |
|
float 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; |
|
} |
|
|
|
bool 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); |
|
|
|
float coneRad[2]; |
|
coneRad[0] = (float)(ls.fallsize * M_PI / 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++ ) |
|
{ |
|
float rad; |
|
rad = float(atan2(corners[j].fX, -corners[j].fZ)); |
|
if( rad > coneRad[0] ) |
|
numPos[0]++; |
|
if( rad < -coneRad[0] ) |
|
numPos[2]++; |
|
rad = float(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; |
|
} |
|
|
|
bool 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; |
|
|
|
float 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; |
|
} |
|
|
|
bool 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); |
|
|
|
bool excluded = false; |
|
if( !liObj->GetUseLight() ) |
|
{ |
|
excluded = true; |
|
} |
|
if( !excluded && liObj->GetExclList() && liObj->GetExclList()->TestFlag(NT_AFFECT_ILLUM) ) |
|
{ |
|
bool 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; |
|
} |
|
|
|
bool 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 ) |
|
{ |
|
bool 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; |
|
} |
|
|
|
bool plLightMapGen::IFindActiveLights(plMaxNode* node) |
|
{ |
|
fActiveLights.SetCount(0); |
|
int i; |
|
for( i = 0; i < fAllLights.GetCount(); i++ ) |
|
{ |
|
IPrepLight(&fAllLights[i], node); |
|
} |
|
|
|
return fActiveLights.GetCount() > 0; |
|
} |
|
|
|
bool 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; |
|
} |
|
|
|
bool plLightMapGen::IReleaseActiveLights() |
|
{ |
|
fActiveLights.SetCount(0); |
|
|
|
return true; |
|
} |
|
|
|
bool plLightMapGen::IWantsMaps(plMaxNode* node) |
|
{ |
|
if( !(node->CanConvert() && node->GetDrawable()) ) |
|
return false; |
|
|
|
return nil != node->GetLightMapComponent(); |
|
} |
|
|
|
bool 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_t initColor = MakeUInt32Color(col.r, col.g, col.b, col.a); |
|
uint32_t* pix = (uint32_t*)bitmap->GetImage(); |
|
uint32_t* pixEnd = ((uint32_t*)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); |
|
} |
|
|
|
plString newMatName = plFormat("{}_{}_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; |
|
|
|
bool sharemaps = node->GetLightMapComponent()->GetShared(); |
|
if( sharemaps ) |
|
{ |
|
objMat = mat; |
|
} |
|
else |
|
{ |
|
objMat = new hsGMaterial; |
|
hsgResMgr::ResMgr()->NewKey(newMatName, objMat, nodeLoc); |
|
|
|
for( i = 0; i < mat->GetNumLayers(); i++ ) |
|
hsgResMgr::ResMgr()->AddViaNotify(mat->GetLayer(i)->GetKey(), 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()); |
|
|
|
plString layName = plFormat("{}_{}_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 |
|
{ |
|
plString mipmapName = plFormat("{}_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 ) |
|
{ |
|
plString compressedName = plFormat("{}_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 = new plLayer; |
|
layer->InitToDefault(); |
|
layKey = hsgResMgr::ResMgr()->NewKey(layName, layer, nodeLoc); |
|
hsgResMgr::ResMgr()->AddViaNotify(mipKey, 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, 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_t 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_t retVal; |
|
|
|
retVal = MakeUInt32Color(accum.r, accum.g, accum.b, 1.f); |
|
|
|
return retVal; |
|
} |
|
|
|
bool 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 = (int)(kTexPerFoot / duDr); |
|
|
|
if( fWidth > kMaxSize ) |
|
fWidth = kMaxSize; |
|
if( fWidth < kMinSize ) |
|
fWidth = kMinSize; |
|
} |
|
else |
|
{ |
|
fWidth = kMinSize; |
|
} |
|
fWidth *= fScale; |
|
fWidth = IPowerOfTwo(fWidth); |
|
|
|
if( dvDr > 0 ) |
|
{ |
|
fHeight = (int)(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; |
|
}
|
|
|