Files
FC1/Editor/LightmapCompiler/IndoorPatchLights.cpp
romkazvo 34d6c5d489 123
2023-08-07 19:29:24 +08:00

2480 lines
96 KiB
C++
Raw Blame History

// ---------------------------------------------------------------------------------------------
// Crytek CryENGINE source code
// History:
// - Created by Marco Corbetta
// - Rewritten by Tim Schroeder
// - Partial rewrite for editor GLM integration (Tim Schroeder)
// - Debug code added (Tim Schroeder)
// ---------------------------------------------------------------------------------------------
#include "stdafx.h"
#include <ILMSerializationManager.h>
#include <float.h>
#include <direct.h>
#include "I3dEngine.h"
#include <list2.h>
#include "IndoorLightPatches.h"
#include "IEntityRenderstate.h"
#include <AABBSV.h>
extern CAASettings gAASettings;
// Needs to be somewhere
ICompilerProgress *g_pIProgress = NULL;
CRasterCubeManager::~CRasterCubeManager()
{
//just make sure all rastercubes were destroyed
Clear();
}
void CRasterCubeManager::AddReference(const CRadMesh* cpRadMesh)
{
if(cpRadMesh->m_pClusterRC == NULL)
return;
std::map<CRasterCubeImpl*, std::set<const CRadMesh*> >::iterator iter = m_mRasterCubeMap.find(cpRadMesh->m_pClusterRC);
assert(iter != m_mRasterCubeMap.end());
(iter->second).insert(cpRadMesh);
}
CRasterCubeImpl* CRasterCubeManager::CreateRasterCube()
{
CRasterCubeImpl* pImpl = new CRasterCubeImpl();
assert(pImpl);
m_mRasterCubeMap.insert(std::pair<CRasterCubeImpl*, std::set<const CRadMesh*> >(pImpl, std::set<const CRadMesh*>()));
return pImpl;
}
const bool CRasterCubeManager::RemoveReference(CRadMesh* cpRadMesh)
{
if(cpRadMesh->m_pClusterRC == NULL)
return false; //there has never been a reference added
std::map<CRasterCubeImpl*, std::set<const CRadMesh*> >::iterator iter = m_mRasterCubeMap.find(cpRadMesh->m_pClusterRC);
cpRadMesh->m_pClusterRC = NULL;
if(iter == m_mRasterCubeMap.end())
return false;//there has never been a reference added
(iter->second).erase(cpRadMesh); //erase from set
if((iter->second).size() < 1) //erase rastercube because it has no more references
{
delete (iter->first); //not needed anymore
m_mRasterCubeMap.erase(iter);
return true;
}
return false;
}
void CRasterCubeManager::Clear()
{
//just make sure all rastercubes were destroyed
for(std::map<CRasterCubeImpl*, std::set<const CRadMesh*> >::iterator iter = m_mRasterCubeMap.begin(); iter != m_mRasterCubeMap.end(); ++iter)
{
delete (iter->first);
}
m_mRasterCubeMap.clear();
}
const Vec3d RotateIntoPlane(const Vec3d& vPlaneNormal0, const Vec3d& vPlaneNormal1, const Vec3d& rInPosition, const Vec3d& rSource0)
{
Vec3d vDir = rInPosition - rSource0;
//first try to rotate
Vec3d vPlaneCross = vPlaneNormal0 % vPlaneNormal1; //this is gonna be the rotation axis
float cfSinPlaneCross = vPlaneCross.GetLength(); //this is the sinus of the angle between both plane normals
if(cfSinPlaneCross < 0.001f)
return rInPosition;
float cfCosPlaneCross = vPlaneNormal0 * vPlaneNormal1;
vPlaneCross *= 1.0f / cfSinPlaneCross; //normalize rotation axis
Vec3d origin = vPlaneCross*(vPlaneCross|vDir);
assert(fabs(cfSinPlaneCross*cfSinPlaneCross+cfCosPlaneCross*cfCosPlaneCross - 1.f) < 0.01f);
Vec3d vRet = (origin + (vDir-origin)*cfCosPlaneCross + (vPlaneCross % vDir)*cfSinPlaneCross);
//now check how accurate this is
const float cfPlaneDist = -(vRet*vPlaneNormal1);
//it was a good rotation, use rotated result
if(fabs(cfPlaneDist) < 0.01f)
{
return (vRet + rSource0);
}
//result of rotation is close to the plane, correct this difference
if(fabs(cfPlaneDist) < 0.1f)
{
vRet = vRet + vPlaneNormal1*cfPlaneDist;
return (vRet + rSource0);
}
//use plane projection, rotation went wrong
if(vDir * vPlaneNormal1 > 0)
vDir *= -1;//opposite direction
const float cfPlaneDist2 = -(vDir*vPlaneNormal1);
Vec3d r = vPlaneNormal1*(cfPlaneDist2);
return (rInPosition + r);
}
CLightScene::CLightScene() : m_uiCurrentPatchNumber(0), m_pCurrentPatch(NULL), m_pISerializationManager(NULL), m_uiStartMemoryConsumption(0), m_puiIndicator(0), m_pOcclSamples(0), m_pSubSamples(0), m_pfColours(0), m_uiSampleCount(0)
{
memset(&m_IndInterface, 0, sizeof(IndoorBaseInterface));
}
CLightScene::~CLightScene()
{
if (m_pISerializationManager)
{
m_pISerializationManager->Release();
m_pISerializationManager = NULL;
}
if(m_puiIndicator)
{
delete [] m_puiIndicator; m_puiIndicator = NULL;
}
if(m_pSubSamples)
{
delete [] m_pSubSamples; m_pSubSamples = NULL;
}
if(m_pOcclSamples)
{
delete [] m_pOcclSamples; m_pOcclSamples = NULL;
}
if(m_pfColours)
{
delete [] m_pfColours; m_pfColours = NULL;
}
}
//check normals to be normalized, trace a warning to tell that this is not correct
const unsigned int CLightScene::CheckNormals(float& rfMaxVariance, const CRadMesh* const pMesh)
{
const bool bNotNormalized = ((pMesh->m_uiFlags & NOT_NORMALIZED_NORMALS_FLAG) == 0)?false : true;
if(bNotNormalized)
rfMaxVariance = sqrtf(pMesh->m_fMaxVariance);
else
rfMaxVariance = 0.f;
const bool bWrongNormals = ((pMesh->m_uiFlags & WRONG_NORMALS_FLAG) == 0)?false : true;
unsigned int uiRet = 0;
if(bNotNormalized)
{
rfMaxVariance = sqrtf(rfMaxVariance);//to retrieve real variance, not the squared one
uiRet |= NOT_NORMALIZED_NORMALS_FLAG;
}
if(bWrongNormals)
uiRet |= WRONG_NORMALS_FLAG;
return uiRet;
}
inline void CLightScene::MergeTangentSpace(CRadVertex& rVertex1, CRadVertex& rVertex2)
{
//quick hack to use a merged normal
Vec3d vNewNormal = (rVertex1.m_vNormal * 0.5f + rVertex2.m_vNormal * 0.5f); vNewNormal.Normalize();
rVertex1.m_vNormal = rVertex2.m_vNormal = vNewNormal;
}
/*idea is as follows:
- every polygon contains a vector of vertex sharing polygon pointers
- so iterate all polygons of all lightmap meshes and compare vertices of all polygons of the other patches
- during computation of colours and normals, if barycentric coordinates fail on the closest polygon,
they are mapped into the vertex sharing polygons to produce the right, smooth value
- this function computes the hash_map and sets the hash indices into the respective polygons
*/
void CLightScene::ComputeSmoothingInformation(const unsigned int uiSmoothAngle, const bool cbTSGeneratedByLM)
{
assert(uiSmoothAngle <= 90);
//will only smmoth an edge if it really is one, otherwise normals are treated differently
static const float epsPos = 0.000001f; //be more correct, vertex position should really be the same
const float epsNormalSmooth = cosf(0.001f/*some threshold*/ + (float)uiSmoothAngle/180.f * 3.14159f); //use a coarser eps for normals, everything which is dotted below 45 degree
//iterate each lightmap patch
for (radpolyit iterPatchOuter=m_lstScenePolys.begin(); iterPatchOuter!=m_lstScenePolys.end(); iterPatchOuter++)
{//outer lightmap mesh loop
CRadPoly *pPatchOuter=(*iterPatchOuter);
//with each remaining one
for (radpolyit iterPatchInner=iterPatchOuter+1; iterPatchInner!=m_lstScenePolys.end();iterPatchInner++)
{//inner lightmap mesh loop
CRadPoly *pPatchInner=(*iterPatchInner);
if(iterPatchOuter != iterPatchInner) //do not consider polygons of the same mesh which are smoothed already
{
for (radpolyit iterPolyOuter=pPatchOuter->m_lstMerged.begin();iterPolyOuter!=pPatchOuter->m_lstMerged.end();iterPolyOuter++)
{//outer lightmap mesh polygon iteration
CRadPoly& rPolyOuter= *(*iterPolyOuter);
bool bMerged[3] = {false,false,false}; //keep trac of the tangent space merging
assert(rPolyOuter.m_lstOriginals.size() == 3);
const int nVerts1 = rPolyOuter.m_lstOriginals.size();
for (radpolyit iterPolyInner=pPatchInner->m_lstMerged.begin();iterPolyInner!=pPatchInner->m_lstMerged.end();iterPolyInner++)
{//inner lightmap mesh polygon iteration
int iFirstFoundIndexOuter = -1, iFirstFoundIndexInner = -1; //index saving first found shared indices
bool bShareCond = false; //to stop the vertex-vertex loop
bool bShareCondFound0 = false; //to notice if at least one vertex was shared
CRadPoly& rPolyInner = *(*iterPolyInner);
assert(rPolyInner.m_lstOriginals.size() == 3);
const int nVerts2 = rPolyInner.m_lstOriginals.size();
int vCount=0; //shared vertex counter, to only consider triangles with sharing edges
for(int k1=0; k1<nVerts1; k1++)
{//outer lightmap mesh polygon per vertex iteration
CRadVertex& rVert1 = rPolyOuter.m_lstOriginals[k1];
for(int k2=0; k2<nVerts2; k2++)
{//inner lightmap mesh polygon per vertex iteration
CRadVertex& rVert2 = rPolyInner.m_lstOriginals[k2];
const float fDot = rVert1.m_vNormal * rVert2.m_vNormal;
if
(
fabs(rVert1.m_vPos.x - rVert2.m_vPos.x) < epsPos &&
fabs(rVert1.m_vPos.y - rVert2.m_vPos.y) < epsPos &&
fabs(rVert1.m_vPos.z - rVert2.m_vPos.z) < epsPos &&
fDot > epsNormalSmooth
)
{
//now check whether this TS was generated here or was correctly loaded
if(cbTSGeneratedByLM)
{
//if shared vertex from pPatchOuter was already merged, use merged result to this shared one
//otherwise compute and apply merged tangent space and normal
if(!bMerged[k1] && rVert2.m_vNormal == rVert2.m_vTNormal)//if second vertex was merged, the tangent normal and vertex normal are different
{
MergeTangentSpace(rVert1, rVert2);
bMerged[k1] = true;
}
else
{
rVert2.m_vNormal = rVert1.m_vNormal;
}
}
vCount++;//found a shared vertex
bShareCondFound0 = true;
if(vCount<2)
{
//save shared indices
iFirstFoundIndexOuter = k1;
iFirstFoundIndexInner = k2;
continue;//shared vertex counter, to only consider triangles with sharing edges
}
//now if both vertices are too close to the first shared one, iterate to the next vertex
if(iFirstFoundIndexOuter == k1)//continue in outer loop
{
vCount--; //correct shared vertex count
k2 = 3; //force outer loop to kick in again
break;
}
if(iFirstFoundIndexInner == k2)//continue in inner loop
{
vCount--; //correct shared vertex count
continue;
}
assert(iFirstFoundIndexOuter != -1 && iFirstFoundIndexInner != -1);
pPatchOuter->m_dwFlags |= SHARED_FLAG; //mark as not to compress due to some sharing polygons
pPatchInner->m_dwFlags |= SHARED_FLAG; //mark as not to compress due to some sharing polygons
//both triangles share two vertices
//prepare second pair values
const unsigned int uiSecondOuter = ((iFirstFoundIndexOuter | (k1 << 8)) | ((iFirstFoundIndexInner | (k2 << 8)) << 16));
const unsigned int uiSecondInner = ((iFirstFoundIndexInner | (k2 << 8)) | ((iFirstFoundIndexOuter | (k1 << 8)) << 16));
rPolyOuter.m_SharingPolygons.push_back(std::pair<CRadPoly*,unsigned int>(&rPolyInner, uiSecondOuter)); //add this polygon to the respective list and vice versa
rPolyInner.m_SharingPolygons.push_back(std::pair<CRadPoly*,unsigned int>(&rPolyOuter, uiSecondInner));
bShareCond = true;
}
if(bShareCond)
break;
}//inner lightmap mesh polygon per vertex iteration
if(bShareCond)
break;
if(bShareCondFound0)
{
//only one vertex was shared, do special encoding
assert(iFirstFoundIndexOuter != -1 && iFirstFoundIndexInner != -1);
pPatchOuter->m_dwFlags |= SHARED_FLAG; //mark as not to compress due to some sharing polygons
pPatchInner->m_dwFlags |= SHARED_FLAG; //mark as not to compress due to some sharing polygons
//both triangles share at least one vertex
//prepare second pair values
const unsigned int uiSecondOuter = ((iFirstFoundIndexOuter | (CRadPoly::scuiOneVertexShareFlag << 8)) | ((iFirstFoundIndexInner | (CRadPoly::scuiOneVertexShareFlag << 8)) << 16));
const unsigned int uiSecondInner = ((iFirstFoundIndexInner | (CRadPoly::scuiOneVertexShareFlag << 8)) | ((iFirstFoundIndexOuter | (CRadPoly::scuiOneVertexShareFlag << 8)) << 16));
rPolyOuter.m_SharingPolygons.push_back(std::pair<CRadPoly*,unsigned int>(&rPolyInner, uiSecondOuter)); //add this polygon to the respective list and vice versa
rPolyInner.m_SharingPolygons.push_back(std::pair<CRadPoly*,unsigned int>(&rPolyOuter, uiSecondInner));
}
}//outer lightmap mesh polygon per vertex iteration
}//inner lightmap mesh polygon iteration
}//outer lightmap mesh polygon iteration
}//(iterPatchOuter != iterPatchInner)
}//inner lightmap mesh loop
}//outer lightmap mesh loop
}
bool CLightScene::ComputeRasterCube(CRasterCubeImpl *pRasterCube,
const std::set<IEntityRender *>& vGeom,
const Matrix33 *pDirLightTransf)
{
// ---------------------------------------------------------------------------------------------
// Build the Raster Cube structure
// ---------------------------------------------------------------------------------------------
UINT iNumTri;
IStatObj *pObj = NULL;
Vec3d vTri[3];
const CObjFace *pFace = NULL;
CIndoorArea *pArea = NULL;
INT iCurFace = 0;
bool bFirstPass;
RasterCubeUserT sUserTri;
Vec3d vEdge1, vEdge2;
CMatInfo *pMat = NULL;
IShader *pEff = NULL;
IShader *pShTempl = NULL;
UINT iNumNonOpaqueSkipped = 0;
UINT iNumDecalsSkipped = 0;
UINT iCurGeom = 0;
IStatObj *pIGeom = NULL;
Matrix44 matEtyMtx;
if (vGeom.empty())
return false;
// ---------------------------------------------------------------------------------------------
// Compute number of triangles in mesh
// ---------------------------------------------------------------------------------------------
std::set<IEntityRender *>::const_iterator itGeom;
iNumTri = 0;
for (std::set<IEntityRender *>::const_iterator itGeom=vGeom.begin(); itGeom!=vGeom.end(); itGeom++)
{
// Skip GLMs that are not marked as shadow casters
if (((* itGeom)->GetRndFlags() & ERF_CASTSHADOWINTOLIGHTMAP) == 0)
continue;
iCurGeom = 0;
while (pIGeom = (* itGeom)->GetEntityStatObj(iCurGeom++, NULL))
{
// Old IdxMesh: iNumTri += pIGeom->GetTriData()->m_nFaceCount;
int iIdxCnt = 0;
CLeafBuffer *pLB = pIGeom->GetLeafBuffer();
if (pLB->IsEmpty())
continue;
pLB->GetIndices(&iIdxCnt);
iNumTri += iIdxCnt / 3;
}
}
#ifdef DISPLAY_MORE_INFO
_TRACE(m_LogInfo, true, "Raster cube contains %i triangles distributed over %i GLMs\r\n", iNumTri, vGeom.size());
#else
_TRACE(m_LogInfo, false, "Raster cube contains %i triangles distributed over %i GLMs\r\n", iNumTri, vGeom.size());
#endif
// ---------------------------------------------------------------------------------------------
// Inititalize the raster cube with the bounding box and the face count
// ---------------------------------------------------------------------------------------------
Vec3d vMin(FLT_MAX, FLT_MAX, FLT_MAX);
Vec3d vMax(FLT_MIN, FLT_MIN, FLT_MIN);
for (std::set<IEntityRender *>::const_iterator itGeom=vGeom.begin(); itGeom!=vGeom.end(); itGeom++)
{
// Skip GLMs that are not marked as shadow casters
if (((* itGeom)->GetRndFlags() & ERF_CASTSHADOWINTOLIGHTMAP) == 0)
continue;
IEntityRender *pCurEtyRend = (* itGeom);
Vec3d vNewMin, vNewMax;
// NOTE: World space AABB
pCurEtyRend->GetBBox(vNewMin, vNewMax);
vMin.x = __min(vMin.x, vNewMin.x);
vMin.y = __min(vMin.y, vNewMin.y);
vMin.z = __min(vMin.z, vNewMin.z);
vMax.x = __max(vMax.x, vNewMax.x);
vMax.y = __max(vMax.y, vNewMax.y);
vMax.z = __max(vMax.z, vNewMax.z);
}
// Transform AABB to light space if required
if (pDirLightTransf)
{
vMin = (* pDirLightTransf) * vMin;
vMax = (* pDirLightTransf) * vMax;
}
if (!pRasterCube->Init(vMin, vMax, iNumTri))
return false;
// ---------------------------------------------------------------------------------------------
// Add triangles
// ---------------------------------------------------------------------------------------------
bFirstPass = true;
while (true)
{
for (std::set<IEntityRender *>::const_iterator itGeom=vGeom.begin(); itGeom!=vGeom.end(); itGeom++)
{
// Skip GLMs that are not marked as shadow casters
if (((* itGeom)->GetRndFlags() & ERF_CASTSHADOWINTOLIGHTMAP) == 0)
continue;
iCurGeom = 0;
while (pObj = (* itGeom)->GetEntityStatObj(iCurGeom++, &matEtyMtx))
{
if (pDirLightTransf)
matEtyMtx *= (* pDirLightTransf);
// IStatObj * pILODLevel = pObj->GetLodObject(0);
CLeafBuffer *pLB = pObj->GetLeafBuffer();
if (pLB->IsEmpty())
continue;
// Get indices and vertices
int iIdxCnt = 0;
unsigned short *pIndices = pLB->GetIndices(&iIdxCnt);
int iVtxStride = 0;
Vec3d *pVertices = reinterpret_cast<Vec3d *> (pLB->GetPosPtr(iVtxStride));
// Loop through all materials
UINT iCurMat;
list2<CMatInfo> *pMaterials = pLB->m_pMats;
for (iCurMat=0; iCurMat<pMaterials->Count(); iCurMat++)
{
CMatInfo cCurMat = (* pMaterials)[iCurMat];
assert(cCurMat.nNumIndices % 3 == 0);
assert(cCurMat.nFirstIndexId + cCurMat.nNumIndices <= iIdxCnt);
// Check if the material is opaque, we don't let non-opaque geometry cast shadows
pEff = cCurMat.shaderItem.m_pShader;
if (pEff)
{
pShTempl = pEff->GetTemplate(-1);
if (pShTempl)
{
CString sShaderName = pShTempl->GetName();
if(sShaderName.Find("templdecal") != -1) //do not compute decals (i love exceptions)
{
iNumDecalsSkipped++;
continue;
}
// Don't let faces cast shadows which are marked as unshadowing, invisible or not opaque
if ((pShTempl->GetFlags2() & EF2_NOCASTSHADOWS) ||
(pShTempl->GetFlags2() & EF2_OPAQUE) == 0 ||
(pShTempl->GetFlags3() & EF3_NODRAW))
{
iNumNonOpaqueSkipped++;
continue;
}
}
}
// Process all triangles
UINT iCurTri = 0;
for (iCurTri=0; iCurTri<cCurMat.nNumIndices / 3; iCurTri++)
{
UINT iBaseIdx = cCurMat.nFirstIndexId + iCurTri * 3;
// Transform from object to world space
Vec3d vWorldSpaceTri[3];
vWorldSpaceTri[0] = matEtyMtx.TransformPointOLD
(* reinterpret_cast<Vec3d *> (&(reinterpret_cast<byte *>(pVertices)[pIndices[iBaseIdx + 0] * iVtxStride])));
vWorldSpaceTri[1] = matEtyMtx.TransformPointOLD
(* reinterpret_cast<Vec3d *> (&(reinterpret_cast<byte *>(pVertices)[pIndices[iBaseIdx + 1] * iVtxStride])));
vWorldSpaceTri[2] = matEtyMtx.TransformPointOLD
(* reinterpret_cast<Vec3d *> (&(reinterpret_cast<byte *>(pVertices)[pIndices[iBaseIdx + 2] * iVtxStride])));
// Vertices
vTri[0].x = vWorldSpaceTri[0].x;
vTri[0].y = vWorldSpaceTri[0].y;
vTri[0].z = vWorldSpaceTri[0].z;
vTri[1].x = vWorldSpaceTri[1].x;
vTri[1].y = vWorldSpaceTri[1].y;
vTri[1].z = vWorldSpaceTri[1].z;
vTri[2].x = vWorldSpaceTri[2].x;
vTri[2].y = vWorldSpaceTri[2].y;
vTri[2].z = vWorldSpaceTri[2].z;
memcpy(&sUserTri.fVertices[0], &vTri[0], sizeof(float) * 3);
memcpy(&sUserTri.fVertices[1], &vTri[1], sizeof(float) * 3);
memcpy(&sUserTri.fVertices[2], &vTri[2], sizeof(float) * 3);
// Face normal
vEdge1.x = vTri[0].x - vTri[1].x;
vEdge1.y = vTri[0].y - vTri[1].y;
vEdge1.z = vTri[0].z - vTri[1].z;
vEdge2.x = vTri[0].x - vTri[2].x;
vEdge2.y = vTri[0].y - vTri[2].y;
vEdge2.z = vTri[0].z - vTri[2].z;
sUserTri.vNormal = vEdge1 ^ vEdge2;
sUserTri.vNormal.Normalize();
// Add it to the raster cube
pRasterCube->PutInTriangle(vTri, sUserTri);
}
}
}
}
// We need to Add/PreProcess twice
if (bFirstPass)
{
char szDebugName[256];
#ifndef WIN64
sprintf(szDebugName,"%x",pRasterCube);
#endif
#ifdef _DEBUG
if (!pRasterCube->PreProcess(szDebugName))
return false;
#else
if (!pRasterCube->PreProcess(NULL))
return false;
#endif
}
else
break;
bFirstPass = false;
}
if (iNumNonOpaqueSkipped != 0)
#ifdef DISPLAY_MORE_INFO
_TRACE(m_LogInfo, true, "Skipped %i non-opaque materials for shadow casting\r\n", iNumNonOpaqueSkipped / 2);
#else
_TRACE(m_LogInfo, false, "Skipped %i non-opaque materials for shadow casting\r\n", iNumNonOpaqueSkipped / 2);
#endif
if (iNumDecalsSkipped != 0)
#ifdef DISPLAY_MORE_INFO
_TRACE(m_LogInfo, true, "Skipped %i decal materials for shadow casting\r\n", iNumDecalsSkipped / 2);
#else
_TRACE(m_LogInfo, false, "Skipped %i decal materials for shadow casting\r\n", iNumDecalsSkipped / 2);
#endif
return true;
}
//! \brief Only used local during generation of light clustering arrangement
struct LightCluster
{
CRasterCubeImpl *pPointLtRC;
std::vector<LMCompLight *> vLights;
Vec3d vMin;
Vec3d vMax;
LightCluster() : vMin(FLT_MAX, FLT_MAX, FLT_MAX), vMax(FLT_MIN, FLT_MIN, FLT_MIN), pPointLtRC(NULL){}
};
//returns true if there is a conservative 2D sun space overlap
static const bool CheckOverlapInSunSpace(const Vec3d& rClusterMin, const Vec3d& rClusterMax, const Vec3d& rGLMMin, const Vec3d& rGLMMax, const Matrix33& rSunSpace, const bool cbUpdateMinMax)
{
//first build vertices of cluster box
static Vec3d vCluster[8];
static float fMinXCluster, fMinYCluster, fMaxXCluster, fMaxYCluster;
if(cbUpdateMinMax)
{
fMinXCluster = FLT_MAX; fMinYCluster = FLT_MAX; fMaxXCluster = -FLT_MAX; fMaxYCluster = -FLT_MAX;
vCluster[0]/*Min,Min,Min*/ = vCluster[1] = vCluster[2] = vCluster[3] = rClusterMin;
vCluster[1].z = rClusterMax.z;/*Min,Min,Max*/
vCluster[2].y = rClusterMax.y;/*Min,Max,Min*/
vCluster[3].x = rClusterMax.x;/*Max,Min,Min*/
vCluster[7] = vCluster[4] = vCluster[5] = vCluster[6] = rClusterMax; /*Max,Max,Max*/
vCluster[4].z = rClusterMin.z;/*Max,Max,Min*/
vCluster[5].y = rClusterMin.y;/*Max,Min,Max*/
vCluster[6].x = rClusterMin.x;/*Min,Max,Max*/
//transform both into sun space
for(int i=0;i<8;i++)
{
vCluster[i] = vCluster[i] * rSunSpace;
}
for(int j=0;j<8;j++)
{
fMinXCluster = (vCluster[j].x < fMinXCluster)?vCluster[j].x:fMinXCluster;
fMinYCluster = (vCluster[j].y < fMinYCluster)?vCluster[j].y:fMinYCluster;
fMaxXCluster = (vCluster[j].x > fMaxXCluster)?vCluster[j].x:fMaxXCluster;
fMaxYCluster = (vCluster[j].y > fMaxYCluster)?vCluster[j].y:fMaxYCluster;
}
}
Vec3d vGLM[8];
vGLM[0]/*Min,Min,Min*/ = vGLM[1] = vGLM[2] = vGLM[3] = rGLMMin;
vGLM[1].z = rGLMMax.z;/*Min,Min,Max*/
vGLM[2].y = rGLMMax.y;/*Min,Max,Min*/
vGLM[3].x = rGLMMax.x;/*Max,Min,Min*/
vGLM[7] = vGLM[4] = vGLM[5] = vGLM[6] = rGLMMax; /*Max,Max,Max*/
vGLM[4].z = rGLMMin.z;/*Max,Max,Min*/
vGLM[5].y = rGLMMin.y;/*Max,Min,Max*/
vGLM[6].x = rGLMMin.x;/*Min,Max,Max*/
//transform both into sun space
for(int i=0;i<8;i++)
{
vGLM[i] = vGLM[i] * rSunSpace;
}
//get min max x and y
float fMinXGLM = FLT_MAX, fMinYGLM = FLT_MAX, fMaxXGLM = -FLT_MAX, fMaxYGLM = -FLT_MAX;
for(int j=0;j<8;j++)
{
fMinYGLM = (vGLM[j].y < fMinYGLM)?vGLM[j].y:fMinYGLM;
fMinXGLM = (vGLM[j].x < fMinXGLM)?vGLM[j].x:fMinXGLM;
fMaxXGLM = (vGLM[j].x > fMaxXGLM)?vGLM[j].x:fMaxXGLM;
fMaxYGLM = (vGLM[j].y > fMaxYGLM)?vGLM[j].y:fMaxYGLM;
}
// Check for overlap
if (fMaxXGLM < fMinXCluster || fMaxYGLM < fMinYCluster)
return false;
if (fMinXGLM > fMaxXCluster || fMinYGLM > fMaxYCluster)
return false;
return true;
}
void CLightScene::SelectObjectsFromChangedLMShadowCaster(
std::vector<std::pair<IEntityRender*, CBrushObject*> >&vNodes,
const std::vector<AABB>& vAABBs,
const Matrix33& rMatSunBasis,
const std::vector<unsigned int>& rvNodeRadMeshIndices)
{
//find out all objects which need to be detected due to receiving shadows from hash changed objects
int iMeshCounter = -1;
for (std::list<CRadMesh *>::iterator itMesh=m_lstRadMeshes.begin(); itMesh!=m_lstRadMeshes.end(); itMesh++)
{
iMeshCounter++;
CRadMesh *pMesh = *itMesh;
if(pMesh == NULL || !(pMesh->m_uiFlags & REBUILD_USED) || !(pMesh->m_uiFlags & HASH_CHANGED))
continue;//was not valid or does not receive lightmaps
//get bounding box for this glm
const AABB& rAABBReceiver = vAABBs[iMeshCounter];
//create shadow volumes for all lights
std::vector<Shadowvolume> vSVs; //shadow volumes
//prepare shadow volumes
bool bHasSunLight = false;
for(std::vector<LMCompLight *>::const_iterator lightIter=pMesh->m_LocalLights.begin(); lightIter != pMesh->m_LocalLights.end(); ++lightIter)
{
const LMCompLight& rLight = *(*lightIter);
if(rLight.eType == eDirectional)
{
bHasSunLight = true;
continue;
}
Shadowvolume sv;
NAABB_SV::AABB_ShadowVolume(rLight.vWorldSpaceLightPos, rAABBReceiver, sv, rLight.fRadius);//build occluder shadow volumes
vSVs.push_back(sv);
}
//iterate all glms and test against these shadow volumes
int iCasterCounter = -1;
for (std::list<CRadMesh *>::iterator itMeshSV=m_lstRadMeshes.begin(); itMeshSV!=m_lstRadMeshes.end(); itMeshSV++)
{
iCasterCounter++;
CRadMesh *pShadowMesh = *itMeshSV;
if(pShadowMesh == NULL || pShadowMesh == pMesh )
continue;//just to make sure
if((pShadowMesh->m_uiFlags & HASH_CHANGED) || !(pShadowMesh->m_uiFlags & RECEIVES_LIGHTMAP))
continue;//don't test with itself or with objects not receiving lightmaps
//get bounding box for this glm
const AABB& rAABBcaster = vAABBs[iCasterCounter];
//first check sun space
if(bHasSunLight && (pShadowMesh->m_uiFlags & RECEIVES_SUNLIGHT))
{
if(CheckOverlapInSunSpace(rAABBReceiver.min, rAABBReceiver.max, rAABBcaster.min, rAABBcaster.max, rMatSunBasis, true))
{
pShadowMesh->m_uiFlags |= REBUILD_USED;
pShadowMesh->m_bReceiveLightmap = true;
continue;
}
}
//now check all other lights
bool bBreak = false;
for(std::vector<Shadowvolume>::const_iterator iter = vSVs.begin(); iter != vSVs.end();++iter)
{
if(bBreak)
break;
if(NAABB_SV::Is_AABB_In_ShadowVolume(*iter, rAABBcaster))
{
pShadowMesh->m_uiFlags |= REBUILD_USED;
pShadowMesh->m_bReceiveLightmap = true;
bBreak = true;
continue;
}
}
}
}
}
void CLightScene::SelectObjectLMReceiverAndShadowCasterForChanges(
std::vector<std::pair<IEntityRender*, CBrushObject*> >& vNodes,
const std::vector<AABB>& vAABBs,
const Matrix33& rMatSunBasis,
const std::vector<unsigned int>& rvNodeRadMeshIndices,
const ELMMode Mode)
{
//gets called in case of not rebuildALL
//goes through all radmeshes and for those who have been/needs to be changed, all objects get detected which are important for correct shadowing
//for each object goes through all ligthsources and create frustum to check all other objects affecting lighting
//flag for all objects whether there are needed or not at all
int iMeshCounter = -1;
for (std::list<CRadMesh *>::iterator itMesh=m_lstRadMeshes.begin(); itMesh!=m_lstRadMeshes.end(); itMesh++)
{
iMeshCounter++;
CRadMesh *pMesh = *itMesh;
if(pMesh == NULL || pMesh->m_bReceiveLightmap == false)
continue;//was not valid or does not receive lightmaps
//get bounding box for this glm
const AABB& rAABBReceiver = vAABBs[iMeshCounter];
//create shadow volumes for all lights
std::vector<Shadowvolume> vSVs; //shadow volumes
//prepare shadow volumes
bool bHasSunLight = false;
for(std::vector<LMCompLight *>::const_iterator lightIter=pMesh->m_LocalLights.begin(); lightIter != pMesh->m_LocalLights.end(); ++lightIter)
{
const LMCompLight& rLight = *(*lightIter);
if(rLight.eType == eDirectional)
{
bHasSunLight = true;
continue;
}
Shadowvolume sv;
NAABB_SV::AABB_ReceiverShadowVolume(rLight.vWorldSpaceLightPos, rAABBReceiver, sv);
vSVs.push_back(sv);
}
//iterate all glms and test against these shadow volumes
int iCasterCounter = -1;
for (std::list<CRadMesh *>::iterator itMeshSV=m_lstRadMeshes.begin(); itMeshSV!=m_lstRadMeshes.end(); itMeshSV++)
{
iCasterCounter++;
CRadMesh *pShadowMesh = *itMeshSV;
if(pShadowMesh == NULL || pShadowMesh == pMesh )
continue;//just to make sure
if((pShadowMesh->m_uiFlags & REBUILD_USED) || ((pShadowMesh->m_uiFlags & CAST_LIGHTMAP) == 0))
continue;//don't test with itself or with objects not casting shadows
if(pShadowMesh->m_bReceiveLightmap == true)
continue;//don't waste time with objects receiving lightmaps as well
//get bounding box for this glm
const AABB& rAABBcaster = vAABBs[iCasterCounter];
//first check sun space
if(bHasSunLight && (pShadowMesh->m_uiFlags & RECEIVES_SUNLIGHT))
{
if(CheckOverlapInSunSpace(rAABBReceiver.min, rAABBReceiver.max, rAABBcaster.min, rAABBcaster.max, rMatSunBasis, true))
{
pShadowMesh->m_uiFlags |= REBUILD_USED;
continue;
}
}
//now check all other lights
bool bBreak = false;
for(std::vector<Shadowvolume>::const_iterator iter = vSVs.begin(); iter != vSVs.end();++iter)
{
if(bBreak)
break;
if(NAABB_SV::Is_AABB_In_ShadowVolume(*iter, rAABBcaster))
{
pShadowMesh->m_uiFlags |= REBUILD_USED;
bBreak = true;
continue;
}
}
}
}
//in case of changes, all objects need to be detected which receive shadows from hash changed objects
if(Mode == ELMMode_CHANGES)
SelectObjectsFromChangedLMShadowCaster(vNodes, vAABBs, rMatSunBasis, rvNodeRadMeshIndices);
//delete unneeded objects
int iCounter = -1;
for (std::list<CRadMesh *>::iterator itMesh=m_lstRadMeshes.begin(); itMesh!=m_lstRadMeshes.end(); itMesh++)
{
iCounter++;
if(*itMesh && !((*itMesh)->m_uiFlags & REBUILD_USED))
{
delete *itMesh;*itMesh = NULL;//will get tested every time it gets accessed
//remove from node list as well
vNodes[rvNodeRadMeshIndices[iCounter]].first = NULL;//will get tested everytime it gets iterated
}
}
}
void CLightScene::CheckLight(LMCompLight& rLight, const int iCurLightIdx)
{
// Too dark ?
if (rLight.fColor[0] + rLight.fColor[1] + rLight.fColor[2] < 0.05f)
{
_TRACE(m_LogInfo, true, "WARNING: Light %i has little or no contribution to the scene, check light color\r\n", iCurLightIdx);
rLight.fColor[0] = rLight.fColor[1] = rLight.fColor[2] = 0.03f;
char text[scuiWarningTextAllocation];
sprintf(text, "Light: %s has little or no contribution to the scene\r\n",(const char*)rLight.m_Name);
m_WarningInfo.insert(std::pair<unsigned int, std::string>(EWARNING_LIGHT_INTENSITY, std::string(text)));
}
// Radius invalid ?
if (rLight.fRadius < 0.001f)
{
_TRACE(m_LogInfo, true, "WARNING: Light %i has negative or unreasonable small radius\r\n", iCurLightIdx);
rLight.fRadius = 0.001f;
char text[scuiWarningTextAllocation];
sprintf(text, "Light: %s has negative or unreasonable small radius\r\n",(const char*)rLight.m_Name);
m_WarningInfo.insert(std::pair<unsigned int, std::string>(EWARNING_LIGHT_RADIUS, std::string(text)));
}
// Frustum invalid ?
if (rLight.eType == eSpotlight)
{
if (rLight.fLightFrustumAngleDegree <= 0.0f || rLight.fLightFrustumAngleDegree > 89.9f )
{
_TRACE(m_LogInfo, true, "WARNING: Spotlight %i has an invalid frustum (%f<>)\r\n", iCurLightIdx, rLight.fLightFrustumAngleDegree);
rLight.fLightFrustumAngleDegree = 89.9f;
char text[scuiWarningTextAllocation];
sprintf(text, "Spotlight: %s has an invalid frustum (%f<>)\r\n",(const char*)rLight.m_Name, rLight.fLightFrustumAngleDegree);
m_WarningInfo.insert(std::pair<unsigned int, std::string>(EWARNING_LIGHT_FRUSTUM, std::string(text)));
}
}
}
bool CLightScene::CreateFromEntity(const IndoorBaseInterface &pInterface, LMGenParam sParam,
std::vector<std::pair<IEntityRender*, CBrushObject*> >& vNodes, CLMLightCollection& cLights,
ICompilerProgress *pIProgress, const ELMMode Mode,
volatile SSharedLMEditorData *pSharedData, const std::set<const CBrushObject*>& vSelectionIndices,
bool &rErrorsOccured)
{
m_uiStartMemoryConsumption = 0; //new run
rErrorsOccured = false;
// ---------------------------------------------------------------------------------------------
// Compute LMs for vNodes
// ---------------------------------------------------------------------------------------------
Init();
std::vector<std::pair<IEntityRender*, CBrushObject*> >::iterator itEty;
CRadMesh *pMesh = NULL;
IEntityRender *pIEtyRend = NULL;
Matrix44 matEtyMtx;
IStatObj *pIGeom = NULL;
CRadPoly *pCurPoly = NULL;
UINT iCurGeom;
DWORD dwStartTime = GetTickCount();
radmeshit itMesh;
g_pIProgress = pIProgress; // TODO
m_sParam = sParam; // TODO
m_LogInfo.clear();
memcpy(&m_IndInterface, &pInterface, sizeof(IndoorBaseInterface));
_TRACE(m_LogInfo, true, "Lightmap compiler started\r\n");
if (!m_pISerializationManager)
m_pISerializationManager = m_IndInterface.m_p3dEngine->CreateLMSerializationManager();
assert(m_pISerializationManager != NULL);
bool bReturn = true;
unsigned int uiMeshesToProcess = 0;
_TRACE(m_LogInfo, true, "Parameters:\r\n", m_sParam.m_fTexelSize);
_TRACE(m_LogInfo, true, "Code Version = 4.0\r\n");
_TRACE(m_LogInfo, true, "Texel size = %f\r\n", m_sParam.m_fTexelSize);
_TRACE(m_LogInfo, true, "Texture resolution = %i\r\n", m_sParam.m_iTextureResolution);
_TRACE(m_LogInfo, true, "Subsampling = %s\r\n", (m_sParam.m_iSubSampling == 9)?"on":"off");
_TRACE(m_LogInfo, true, "Generate Occlusion maps = %s\r\n", (m_sParam.m_bGenOcclMaps)?"True":"False");
_TRACE(m_LogInfo, true, "Shadows = %s\r\n", m_sParam.m_bComputeShadows ? "True" : "False");
_TRACE(m_LogInfo, true, "Use sunLight = %s\r\n", m_sParam.m_bUseSunLight? "True" : "False");
_TRACE(m_LogInfo, true, "Smoothing angle = %i degree\r\n", m_sParam.m_uiSmoothingAngle);
_TRACE(m_LogInfo, true, "Use spotlights as pointlights = %s\r\n", m_sParam.m_bSpotAsPointlight ? "True" : "False");
if(m_sParam.m_bUseSunLight)
_TRACE(m_LogInfo, true, "\r\nProcessing %i GLMs / %i lights + sunlight + %i occlusion map lights\r\n", vNodes.size(), cLights.GetLights().size(), cLights.OcclLightSize());
else
_TRACE(m_LogInfo, true, "\r\nProcessing %i GLMs / %i lights + %i occlusion map lights\r\n", vNodes.size(), cLights.GetLights().size(), cLights.OcclLightSize());
//load texture data if needed (for changes to compare hashs)
if (Mode != ELMMode_ALL) m_pISerializationManager->Load(m_IndInterface.m_p3dEngine->GetFilePath(LM_EXPORT_FILE_NAME), false/*load no textures*/);
// Check lights
UINT iCurLightIdx = 0;
for (std::vector<LMCompLight>::iterator itLight=cLights.GetLights().begin(); itLight!=cLights.GetLights().end(); itLight++)
{
CheckLight(*itLight, iCurLightIdx);
iCurLightIdx++;
}
bool bNewZip = true;
if (sParam.m_bOnlyExportLights || Mode != ELMMode_ALL)
bNewZip = false;
_TRACE(m_LogInfo, true, "Exporting static light sources to '%s'...\r\n", LM_STAT_LIGHTS_EXPORT_FILE_NAME);
string strLMExportFile = m_IndInterface.m_p3dEngine->GetFilePath(LM_STAT_LIGHTS_EXPORT_FILE_NAME);
if (!m_pISerializationManager->ExportDLights(strLMExportFile.c_str(), (const CDLight **) &cLights.GetSrcLights()[0],
(UINT) cLights.GetSrcLights().size(),bNewZip))
{
assert(false);
_TRACE(m_LogInfo, true, "ERROR: Exporting of lightsources to '%s' failed !\r\n", strLMExportFile.c_str());
char text[scuiWarningTextAllocation];
sprintf(text, "Exporting of lightsources to '%s' failed !\r\n", strLMExportFile.c_str());
m_WarningInfo.insert(std::pair<unsigned int, std::string>(EWARNING_LIGHT_EXPORT_FAILED, std::string(text)));
}
if (sParam.m_bOnlyExportLights)
{
rErrorsOccured = (m_WarningInfo.size() != 0);
return true;
}
if(pSharedData != NULL)
DisplayMemStats(pSharedData);
// Add sun light
LMCompLight sSunLight;
if (m_sParam.m_bUseSunLight)
{
sSunLight.fColor[0] = m_IndInterface.m_p3dEngine->GetSunColor().x * 0.5f;
sSunLight.fColor[1] = m_IndInterface.m_p3dEngine->GetSunColor().y * 0.5f;
sSunLight.fColor[2] = m_IndInterface.m_p3dEngine->GetSunColor().z * 0.5f;
sSunLight.eType = eDirectional;
sSunLight.vDirection = Vec3d(0, 0, 0) - m_IndInterface.m_p3dEngine->GetSunPosition();
sSunLight.vDirection.Normalize();
sSunLight.fRadius = FLT_MAX;
sSunLight.m_bFakeRadiosity = false;
sSunLight.m_bDot3 = true;
sSunLight.m_bOcclType = true;
sSunLight.m_CompLightID.first = 0xFFFF;
sSunLight.m_CompLightID.second = 0xFFFF;
cLights.GetLights().push_back(sSunLight);
_TRACE(m_LogInfo, true, "Sunlight with direction %f %f %f added\r\n",
sSunLight.vDirection.x, sSunLight.vDirection.y, sSunLight.vDirection.z);
}
_TRACE(m_LogInfo, true, "Preparing meshes (tangents, AABBs, plane equations, local lightsources, etc.)... ");
std::vector<AABB> vAABBs;
vAABBs.reserve(vNodes.size());
std::vector<unsigned int> vNodeRadMeshIndices; vNodeRadMeshIndices.reserve(vNodes.size());//saves per added mesh the node index
int iNodeIndex= -1;
for (itEty=vNodes.begin(); itEty!=vNodes.end(); itEty++)
{
iNodeIndex++;
pIEtyRend = itEty->first;
if(pIEtyRend == NULL)
continue;
iCurGeom = 0;
if(pSharedData != NULL)
DisplayMemStats(pSharedData);
if(pSharedData != NULL && pSharedData->bCancelled == true)
{
break;
}
pIGeom = pIEtyRend->GetEntityStatObj(iCurGeom++, &matEtyMtx);
if(pIGeom == NULL)
continue;
pMesh = new CRadMesh;
std::vector<LMCompLight>::iterator itLight;
// Add the local lightsources (based on attenuation)
for (itLight=cLights.GetLights().begin(); itLight!=cLights.GetLights().end(); itLight++)
{
LMCompLight& cCurLight = (* itLight);
Vec3d vNewMin, vNewMax;
// Don't add sunlight for GLMs which are in a vis area and are not connected to the outdoors
if (pIEtyRend->GetEntityVisArea() != NULL && cCurLight.eType == eDirectional)
if (!pIEtyRend->GetEntityVisArea()->IsConnectedToOutdoor())
continue;
if(cCurLight.eType == eDirectional)
{
pMesh->m_uiFlags |= RECEIVES_SUNLIGHT;
pMesh->m_uiFlags |= ONLY_SUNLIGHT;
pMesh->m_LocalLights.push_back(&cCurLight);
pMesh->m_LocalOcclLights.push_back(&cCurLight);
continue;
}
pIEtyRend->GetBBox(vNewMin, vNewMax);
if (Overlap::Sphere_AABB(
Sphere(cCurLight.vWorldSpaceLightPos, cCurLight.fRadius),
MakeSafeAABB(vNewMin, vNewMax)))
{
if(cCurLight.uiFlags & DLF_THIS_AREA_ONLY)
{
if(cCurLight.pVisArea != 0 && pIEtyRend->GetEntityVisArea()!=0)
{
if(pIEtyRend->GetEntityVisArea() != cCurLight.pVisArea)
{
// try also portal volumes
const bool bNearFound =
pIEtyRend->GetEntityVisArea()->FindVisArea((IVisArea * )(cCurLight.pVisArea),
1+/*int((cLight.uiFlags & DLF_THIS_AREA_ONLY)==0)+*/int(((IVisArea * )(cCurLight.pVisArea))->IsPortal()), false);
if(!bNearFound)
continue; // areas do not much
}
}
else
if(cCurLight.pVisArea != 0 || pIEtyRend->GetEntityVisArea()!=0)
{
continue;
}
}
pMesh->m_uiFlags &= ~ONLY_SUNLIGHT;
if(cCurLight.m_bCastShadow)
pMesh->m_LocalLights.push_back(&cCurLight);
if(cCurLight.m_bOcclType)
pMesh->m_LocalOcclLights.push_back(&cCurLight);
}
}
// Call after putting in the light sources to get the right HashValue
pMesh->CreateEmpty(pIEtyRend, matEtyMtx);
const CBrushObject *pBrushObject = itEty->second;
if(!pMesh->FillInValues(pIEtyRend, pBrushObject, matEtyMtx) || pMesh->m_lstOldPolys.size() <= 0)
{
delete pMesh; pMesh = NULL;
//remove from node list
itEty->first = NULL;//will get tested everytime it gets iterated
}
else
{//if at least one polygon has been added
m_lstRadMeshes.push_back(pMesh);
//now check for update demand
if (Mode == ELMMode_CHANGES)
{
DWORD dwOldHash = m_pISerializationManager->GetHashValue(pIEtyRend->GetEditorObjectId());
DWORD dwNewHash = pMesh->GetHashValue();
if (dwOldHash == dwNewHash)
{
pMesh->m_bReceiveLightmap = false;
}
else
{
pMesh->m_uiFlags |= (REBUILD_USED | HASH_CHANGED);
uiMeshesToProcess++;
}
}
else
{
if(Mode == ELMMode_SEL)
{
if(vSelectionIndices.find(pBrushObject) == vSelectionIndices.end())
pMesh->m_bReceiveLightmap = false;//this will mark this glm as not selected
else
{
pMesh->m_uiFlags |= REBUILD_USED;
uiMeshesToProcess++;
}
}
}
//create bounding box
Vec3 min, max;
pIEtyRend->GetBBox(min, max);
vAABBs.push_back(MakeSafeAABB(min, max));
vNodeRadMeshIndices.push_back((unsigned int)iNodeIndex);
}
}
// Create light space transform for sun light
Matrix33 matSunBasis;
Vec3 vAxisA, vAxisB;
GetOtherBaseVec(sSunLight.vDirection, vAxisA, vAxisB);
matSunBasis.SetColumn(0, vAxisA);
matSunBasis.SetColumn(1, vAxisB);
matSunBasis.SetColumn(2, sSunLight.vDirection);
matSunBasis.Transpose();
if (Mode != ELMMode_ALL)
SelectObjectLMReceiverAndShadowCasterForChanges(vNodes, vAABBs, matSunBasis, vNodeRadMeshIndices, Mode);
if(pSharedData != NULL)
DisplayMemStats(pSharedData);
_TRACE(m_LogInfo, true, "done\r\n");
if(m_sParam.m_bComputeShadows && (pSharedData == NULL || (pSharedData != NULL && pSharedData->bCancelled == false)))
{
if(pSharedData != NULL)
DisplayMemStats(pSharedData);
_TRACE(m_LogInfo, true, "\r\nComputing clusters...");
{
// Make each GLM a cluster
for (itEty=vNodes.begin(); itEty!=vNodes.end(); itEty++)
{
IEntityRender *pCurEtyRend = itEty->first;
if(pCurEtyRend == NULL)
continue;
Vec3 vNewMin, vNewMax;
GLMCluster sNewCluster;
pCurEtyRend->GetBBox(vNewMin, vNewMax);
sNewCluster.vMin = vNewMin;
sNewCluster.vMax = vNewMax;
sNewCluster.vGLMsAffectingCluster.insert(pCurEtyRend);
m_lstClusters.push_back(sNewCluster);
}
std::list<GLMCluster>::iterator itCluster, itClusterSearch,itClusterStart;
// Merge clusters based on spatial proximity
const float fMaxClusterSize = 25.0f;
#ifdef DISPLAY_MORE_INFO
_TRACE(m_LogInfo, true, "Merge clusters based on spatial proximity (MaxClusterSize = %f)...\r\n", fMaxClusterSize);
#else
_TRACE(m_LogInfo, false, "Merge clusters based on spatial proximity (MaxClusterSize = %f)...\r\n", fMaxClusterSize);
#endif
for (itCluster=m_lstClusters.begin(); itCluster!=m_lstClusters.end(); ++itCluster)
{
if(pSharedData != NULL)
DisplayMemStats(pSharedData);
itClusterStart=itCluster;++itClusterStart;
for (itClusterSearch=itClusterStart; itClusterSearch!=m_lstClusters.end();)
{
Vec3 vOldDimensions = (* itCluster).vMax - (* itCluster).vMin;
vOldDimensions[0] = (* itCluster).vMax.x - (* itCluster).vMin.x;
vOldDimensions[1] = (* itCluster).vMax.y - (* itCluster).vMin.y;
vOldDimensions[2] = (* itCluster).vMax.z - (* itCluster).vMin.z;
Vec3 vMin, vMax; // Bounding box
vMin.x = __min((* itCluster).vMin.x, (* itClusterSearch).vMin.x);
vMin.y = __min((* itCluster).vMin.y, (* itClusterSearch).vMin.y);
vMin.z = __min((* itCluster).vMin.z, (* itClusterSearch).vMin.z);
vMax.x = __max((* itCluster).vMax.x, (* itClusterSearch).vMax.x);
vMax.y = __max((* itCluster).vMax.y, (* itClusterSearch).vMax.y);
vMax.z = __max((* itCluster).vMax.z, (* itClusterSearch).vMax.z);
Vec3 vNewDimensions = vMax - vMin;
// Already too long side would be even longer -> skip merge process
if (vOldDimensions.x > fMaxClusterSize && vNewDimensions.x>vOldDimensions.x) { ++itClusterSearch; continue; }
if (vOldDimensions.y > fMaxClusterSize && vNewDimensions.y>vOldDimensions.y) { ++itClusterSearch; continue; }
if (vOldDimensions.z > fMaxClusterSize && vNewDimensions.z>vOldDimensions.z) { ++itClusterSearch; continue; }
const float fIntersectEpsilon = 1.0f;
const Vec3 vIntersectEpsilon(fIntersectEpsilon, fIntersectEpsilon, fIntersectEpsilon);
if (Overlap::AABB_AABB(
Vec3(0, 0, 0), AABB((* itCluster).vMin - vIntersectEpsilon, (* itCluster).vMax + vIntersectEpsilon),
Vec3(0, 0, 0), AABB((* itClusterSearch).vMin - vIntersectEpsilon, (* itClusterSearch).vMax + vIntersectEpsilon)))
{
// Merge AABB
(* itCluster).vMin = vMin;
(* itCluster).vMax = vMax;
// Merge GLM lists
for (std::set<IEntityRender*>::iterator it = (* itClusterSearch).vGLMsAffectingCluster.begin();
it != (* itClusterSearch).vGLMsAffectingCluster.end(); ++it)
{
(* itCluster).vGLMsAffectingCluster.insert( *it );
}
if (itClusterSearch == itClusterStart) // delete the first element?
++itClusterStart;
// Remove original
m_lstClusters.erase(itClusterSearch);
itClusterSearch = itClusterStart; // Restart
continue;
}
++itClusterSearch;
}
}
_TRACE(m_LogInfo, true, "done\r\n");
if(m_lstClusters.size() > 1)
_TRACE(m_LogInfo, true, "%i clusters created\r\n", m_lstClusters.size());
else
if(m_lstClusters.size() == 1)
_TRACE(m_LogInfo, true, "1 cluster created\r\n");
// Put all GLMs in clusters which can potentially shadow each other (overlap along the light's axis)
if (m_sParam.m_bUseSunLight)
{
UINT iNumPotentialShadowCasters = 0;
#ifdef DISPLAY_MORE_INFO
_TRACE(m_LogInfo, true, "Put all GLMs in clusters which can potentially shadow each other because of sunlight...\r\n");
#else
_TRACE(m_LogInfo, false, "Put all GLMs in clusters which can potentially shadow each other because of sunlight...\r\n");
#endif
for (itCluster=m_lstClusters.begin(); itCluster!=m_lstClusters.end(); ++itCluster)
{
bool bUpdateMinMax = true;
for (itEty=vNodes.begin(); itEty!=vNodes.end(); itEty++)
{
IEntityRender *pCurEtyRend = itEty->first;
if(pCurEtyRend == NULL)
continue;
Vec3 vGLMLightSpaceMin, vGLMLightSpaceMax;
pCurEtyRend->GetBBox(vGLMLightSpaceMin, vGLMLightSpaceMax);
if(!CheckOverlapInSunSpace((* itCluster).vMin, (* itCluster).vMax, vGLMLightSpaceMin, vGLMLightSpaceMax, matSunBasis, bUpdateMinMax))
{
bUpdateMinMax = false;
continue;
}
bUpdateMinMax = true;
(* itCluster).vGLMsAffectingCluster.insert(pCurEtyRend);
iNumPotentialShadowCasters++;
}
}
_TRACE(m_LogInfo, true, "%i potential sunlight shadow casters added\r\n", iNumPotentialShadowCasters);
}
// Add shadow casters to clusters
#ifdef DISPLAY_MORE_INFO
_TRACE(m_LogInfo, true, "Put GLMs in clusters which can potentially shadow on them...\r\n");
#else
_TRACE(m_LogInfo, false, "Put GLMs in clusters which can potentially shadow on them...\r\n");
#endif
for (itCluster=m_lstClusters.begin(); itCluster!=m_lstClusters.end(); ++itCluster)
{
if(pSharedData != NULL)
DisplayMemStats(pSharedData);
std::vector<Shadowvolume> vSVs; //shadow volumes
AABB aabbCluster((* itCluster).vMin, (* itCluster).vMax);
// Find all light sources which overlap with this cluster
for (std::vector<LMCompLight>::const_iterator itLight=cLights.GetLights().begin(); itLight!=cLights.GetLights().end(); itLight++)
{
if ((*itLight).eType != ePoint && (*itLight).eType != eSpotlight)
continue;
if (Overlap::Sphere_AABB(
Sphere((* itLight).vWorldSpaceLightPos, (* itLight).fRadius),
MakeSafeAABB((* itCluster).vMin, (* itCluster).vMax)))
{
// Light lits cluster, create shadow volume
Shadowvolume sv;
NAABB_SV::AABB_ReceiverShadowVolume((*itLight).vWorldSpaceLightPos, aabbCluster, sv);
vSVs.push_back(sv);
}
}
// Find new shadow casters and merge list with old set
for (itEty=vNodes.begin(); itEty!=vNodes.end(); itEty++)
{
IEntityRender *pCurEtyRend = itEty->first;
if(pCurEtyRend == NULL)
continue;
Vec3 vEtyMin, vEtyMax;
pCurEtyRend->GetBBox(vEtyMin, vEtyMax);
const AABB aabbCaster(MakeSafeAABB(vEtyMin, vEtyMax));
//now check all other lights
bool bBreak = false;
for(std::vector<Shadowvolume>::const_iterator iter = vSVs.begin(); iter != vSVs.end();++iter)
{
if(bBreak)
break;
if(NAABB_SV::Is_AABB_In_ShadowVolume(*iter, aabbCaster))
{
bBreak = true;
(* itCluster).vGLMsAffectingCluster.insert(pCurEtyRend);
continue;
}
}
}
}
// Compute raster cubes for clusters
UINT iNumClusters = m_lstClusters.size();
UINT iCurCluster = 0;
_TRACE(m_LogInfo, true, "Computing raster cubes for light cluster...");
for (itCluster=m_lstClusters.begin(); itCluster!=m_lstClusters.end(); itCluster++)
{
if(pSharedData != NULL)
DisplayMemStats(pSharedData);
if(pSharedData != NULL && pSharedData->bCancelled == true)
{
break;
}
// AABB in world space
AABB cWorldSpaceAABB(MakeSafeAABB((* itCluster).vMin, (* itCluster).vMax));
CRasterCubeImpl* pImpl = m_RasterCubeManager.CreateRasterCube();
// Find GLMs in RC AABB and assign this RC to them
std::list<CRadMesh *>::iterator itMesh;
UINT iNumGLMsContained = 0;
for (itMesh=m_lstRadMeshes.begin(); itMesh!=m_lstRadMeshes.end(); itMesh++)
{
if(*itMesh == NULL)
continue;
assert((* itMesh)->m_pESource);
IEntityRender *pCurEtyRend = (* itMesh)->m_pESource;
Vec3 vGLMMin, vGLMMax;
pCurEtyRend->GetBBox(vGLMMin, vGLMMax);
if (vGLMMin.x < cWorldSpaceAABB.min.x ||
vGLMMin.y < cWorldSpaceAABB.min.y ||
vGLMMin.z < cWorldSpaceAABB.min.z)
{
continue;
}
if (vGLMMax.x > cWorldSpaceAABB.max.x ||
vGLMMax.y > cWorldSpaceAABB.max.y ||
vGLMMax.z > cWorldSpaceAABB.max.z)
{
continue;
}
(* itMesh)->m_pClusterRC = pImpl;
m_RasterCubeManager.AddReference((* itMesh));
iNumGLMsContained++;
}
#ifdef DISPLAY_MORE_INFO
_TRACE(m_LogInfo, true, "Computing raster cube for light cluster %i of %i with %i GLMs contained in it / %i GLMs affecting it located" \
" at (%f, %f, %f) - (%f, %f, %f)\r\n",
++iCurCluster, iNumClusters, iNumGLMsContained, (* itCluster).vGLMsAffectingCluster.size(),
(* itCluster).vMin.x, (* itCluster).vMin.y, (* itCluster).vMin.z,
(* itCluster).vMax.x, (* itCluster).vMax.y, (* itCluster).vMax.z);
#else
_TRACE(m_LogInfo, false, "Computing raster cube for light cluster %i of %i with %i GLMs contained in it / %i GLMs affecting it located" \
" at (%f, %f, %f) - (%f, %f, %f)\r\n",
++iCurCluster, iNumClusters, iNumGLMsContained, (* itCluster).vGLMsAffectingCluster.size(),
(* itCluster).vMin.x, (* itCluster).vMin.y, (* itCluster).vMin.z,
(* itCluster).vMax.x, (* itCluster).vMax.y, (* itCluster).vMax.z);
#endif
ComputeRasterCube(pImpl, (* itCluster).vGLMsAffectingCluster);
}
}
}
if(pSharedData == NULL || (pSharedData != NULL && pSharedData->bCancelled == false))
{
if(pSharedData != NULL)
DisplayMemStats(pSharedData);
// Compute BB
Vec3d vMinBB(FLT_MAX, FLT_MAX, FLT_MAX);
Vec3d vMaxBB(FLT_MIN, FLT_MIN, FLT_MIN);
for (itEty=vNodes.begin(); itEty!=vNodes.end(); itEty++)
{
IEntityRender *pCurEtyRend = itEty->first;
if(pCurEtyRend == NULL)
continue;
Vec3d vNewMin, vNewMax;
pCurEtyRend->GetBBox(vNewMin, vNewMax);
vMinBB.x = __min(vMinBB.x, vNewMin.x);
vMinBB.y = __min(vMinBB.y, vNewMin.y);
vMinBB.z = __min(vMinBB.z, vNewMin.z);
vMaxBB.x = __max(vMaxBB.x, vNewMax.x);
vMaxBB.y = __max(vMaxBB.y, vNewMax.y);
vMaxBB.z = __max(vMaxBB.z, vNewMax.z);
}
if (!Create(pInterface, vMinBB, vMaxBB, pSharedData, Mode, (Mode == ELMMode_ALL)?m_lstRadMeshes.size() : uiMeshesToProcess))
{
rErrorsOccured = (m_WarningInfo.size() != 0);
bReturn = false;
}
if(pSharedData != NULL && pSharedData->bCancelled)
{
//free memory
for (radpolyit i=m_lstScenePolys.begin();i!=m_lstScenePolys.end();++i)
{
CRadPoly *pPoly=(*i);
//no lightmap for this poly
delete pPoly; *i = pPoly = NULL;
}
m_lstScenePolys.clear();
m_lstAssignQueue.clear();
delete m_pCurrentPatch; m_pCurrentPatch = NULL;
}
}
// Free raster cubes for light clusters
m_RasterCubeManager.Clear();
//make sure all rad meshes get deallocated
for (radmeshit itMesh=m_lstRadMeshes.begin(); itMesh!=m_lstRadMeshes.end(); itMesh++)
{
delete *itMesh;
*itMesh = NULL;
}
Reset();
//release for the sake of memory deallocation
if (m_pISerializationManager)
{
m_pISerializationManager->Release();
m_pISerializationManager = NULL;
}
if(pSharedData != NULL)
DisplayMemStats(pSharedData);
if(bReturn)
{
unsigned int uiSec = (GetTickCount() - dwStartTime + 499) / 1000;
const unsigned int cuiMinutes = uiSec / 60;
uiSec -= cuiMinutes * 60;
_TRACE(m_LogInfo, true, "Total computation time was %i min %i s\r\n", cuiMinutes, uiSec);
_TRACE(m_LogInfo, true, "Lightmap compiler successfully finished\r\n");
_TRACE("Updating / reloading textures...\r\n");
}
rErrorsOccured = (m_WarningInfo.size() != 0);
//now write log info
WriteLogInfo();
return bReturn;
}
void CLightScene::Shadow(LMCompLight& cLight, UINT& iNumHit, CRadMesh *pMesh, CRadPoly *pSource, const CRadVertex& Vert, const Vec3d& vSamplePos)
{
if (pMesh->m_pClusterRC == NULL)
iNumHit++;
CAnyHit cAnyHit;
// Ray from vertex to light surface sample point
// Vec3d vRayDir = pLight->m_vObjectSpacePos - vSamplePos;
// Vec3d vRayDirNormalized = pLight->m_vObjectSpacePos - vSamplePos;
Vec3d vRayDir, vRayDirNormalized;
float fRayLen;
if (cLight.eType == eDirectional)
{
vRayDirNormalized = vRayDir = -cLight.vDirection/* * 1500.0f*/; // TODO
fRayLen = 2000.0f;
}
else
{
vRayDirNormalized = vRayDir = cLight.vWorldSpaceLightPos - vSamplePos;
vRayDirNormalized.Normalize();
fRayLen = vRayDir.Length();
}
Vec3d vRayOrigin = vSamplePos;
cAnyHit.SetupRay(vRayDirNormalized,
vRayOrigin,
fRayLen,
m_sParam.m_fTexelSize,
pSource);
// Plane equation
// TODO: Move into SetupRay()
// cAnyHit.m_vPNormal = Vert.m_vNormal;
cAnyHit.m_fD = -(pSource->m_Plane.n * Vert.m_vPos); // Inverted ?
cAnyHit.m_vPNormal = pSource->m_Plane.n;
// Check the last intersection first to exploit coherence
bool bSkip = false;
if ((cLight.m_pLastIntersectionRasterCube == pMesh->m_pClusterRC) && cLight.m_pLastIntersection)//do not use cached result from deleted rastercube
{
float fDist = FLT_MAX;
cAnyHit.ReturnElement(* cLight.m_pLastIntersection, fDist);
if (cAnyHit.IsIntersecting())
bSkip = true;
}
if (!bSkip)
{
if (cLight.eType == eDirectional)
{
// Use raster cube of cluster for current mesh
if (pMesh->m_pClusterRC != NULL)
{
// Check occlusion
pMesh->m_pClusterRC->GatherRayHitsDirection(
CVector3D(vRayOrigin.x, vRayOrigin.y, vRayOrigin.z),
CVector3D(vRayDirNormalized.x, vRayDirNormalized.y, vRayDirNormalized.z),
cAnyHit);
if (!cAnyHit.IsIntersecting())
iNumHit++;
else
{
cLight.m_pLastIntersection = cAnyHit.m_pHitResult;
cLight.m_pLastIntersectionRasterCube = pMesh->m_pClusterRC;
}
}
else
iNumHit++;
}
else
{
if (pMesh->m_pClusterRC != NULL)
{
// Check occlusion using raster cube for the current lightsource
pMesh->m_pClusterRC->GatherRayHitsDirection(
CVector3D(vRayOrigin.x, vRayOrigin.y, vRayOrigin.z),
CVector3D(vRayDirNormalized.x, vRayDirNormalized.y, vRayDirNormalized.z),
cAnyHit);
if (!cAnyHit.IsIntersecting())
iNumHit++;
else
{
cLight.m_pLastIntersection = cAnyHit.m_pHitResult;
cLight.m_pLastIntersectionRasterCube = pMesh->m_pClusterRC;
}
}
else
iNumHit++;
}
}
}
static const bool GetLightIntensity(const LMCompLight& rLight, float& rfIntens, const CRadVertex& rVertex, const Vec3d& rvLightDir )
{
rfIntens = 1.0f;
// Backface culling
float fCos = rvLightDir * rVertex.m_vNormal;
if (fCos <= 0.01f)
return false;
if (rLight.eType != eDirectional)
{
const float fRadius = rLight.fRadius;
const float fRadius2 = fRadius * fRadius;
const float fDist2 = GetSquaredDistance(rLight.vWorldSpaceLightPos, rVertex.m_vPos);
if (fDist2 > fRadius2)
return false; // Surface point outside of light radius
assert(!_isnan(fRadius));
assert(fRadius > 0);
if (fRadius <= 0)
return false;
// 1/linear falloff
if(fDist2 > 0.0001f)
rfIntens = 1.0f - sqrtf(fDist2) / (fRadius);
}
return true;
}
static inline const bool SumDot3Values(const LMCompLight& cLight, float& fIntens, float& fLambLumSum, float& fLumSum, float& fColorRLamb, float& fColorGLamb, float& fColorBLamb, const LMCompLight& rLight, Vec3d& rvSumLightDir, const Vec3d& rvLightDir, const CRadVertex& rVertex)
{
bool bApplyBetterLighting = false;
// ---------------------------------------------------------------------------------------------
// Add in light color and direction
// ---------------------------------------------------------------------------------------------
{
// Divide by 2 to compensate for higher scaling during rendering
const float fCos = rvLightDir * rVertex.m_vNormal;
float fLambert = 1.0f;
if(!cLight.m_bFakeRadiosity)//apply better_lighting model
{
// Modulate intensity with Lambert factor
fLambert = max(0,fCos);
if(cLight.m_bDot3)
bApplyBetterLighting = true;
}
else
{
if (fCos <= 0.0001f)
fLambert = 0.0f;
}
#ifdef APPLY_COLOUR_FIX
fColorRLamb += rLight.fColor[0] * fLambert * fIntens;
fColorGLamb += rLight.fColor[1] * fLambert * fIntens;
fColorBLamb += rLight.fColor[2] * fLambert * fIntens;
fIntens *= 0.5f;
#else
fIntens *= 0.5f;
fColorRLamb += rLight.fColor[0] * fLambert * fIntens;
fColorGLamb += rLight.fColor[1] * fLambert * fIntens;
fColorBLamb += rLight.fColor[2] * fLambert * fIntens;
#endif
// Accumulate light direction, weight based on amount of light arriving at the vertex
float fLightLum = (rLight.fColor[0] + rLight.fColor[1] + rLight.fColor[2]) * fIntens * LightInfluence(fCos);
if(cLight.m_bDot3)//has influence to the dot3 vector
{
rvSumLightDir += rvLightDir * fLightLum;
fLambLumSum+= fLambert*fLightLum;
}
else
{
rvSumLightDir += rVertex.m_vNormal * fLightLum;
fLambLumSum += fLightLum;//don't apply lambert factor here
}
fLumSum += fLightLum;
}
return bApplyBetterLighting;
}
static void GetDot3Sample(float& fLM_DP3LerpFactor, Vec3d& rvLightDir, float fLumSum, float fLambLumSum, const CRadVertex& rVert, float& fColorRLamb, float& fColorGLamb, float& fColorBLamb, const bool cbApplyBetterLighting = false)
{
// ---------------------------------------------------------------------------------------------
// DOT3 light direction / lerp factor
// ---------------------------------------------------------------------------------------------
{
// Intensity of DOT3 is based on how accurate our light vector represents the sum of all
// light vectors. In the ideal case (all light vectors from the same direction) the length
// of the accumulated light vector (fLen) is equal to the length of all individual light vectors (fLumSum)
static const float scfLumSumThreshold = 0.01f;
if (fLumSum < scfLumSumThreshold)
{
fLM_DP3LerpFactor = 0.0f;
fLumSum = 0.f;
rvLightDir.x = rvLightDir.y = rvLightDir.z = 0.f;
}
else
{
const float fLen = rvLightDir.Length();
fLM_DP3LerpFactor = fLen / fLumSum;
rvLightDir.Normalize();
#ifndef REDUCE_DOT3LIGHTMAPS_TO_CLASSIC
if(cbApplyBetterLighting) //if to reduce to classic lightmaps we need to apply the new lighting model to take the normals into account
#endif
{
// Calculate lightmap correction factor (lambert calculation)
// *fLumSum/max(0.01f,fLambLumSum) to correct the already applied lambert calculation
// (Vert.m_vNormal*rvLightDir) * fLM_DP3LerpFactor to ???
const float fDot = max(0.f,(rVert.m_vNormal * rvLightDir));
const float fInvLambCorr = fDot * fLM_DP3LerpFactor * (fLumSum/max(scfLumSumThreshold*0.1f,fLambLumSum));
{
// correction depends on the lerp factor (fInvLambCorr is for full directional case)
// fLM_DP3LerpFactor=1 apply corr 100% *=fInvLambCorr
// fLM_DP3LerpFactor<1 apply corr less *=((fInvLambCorr-1)*fLM_DP3LerpFactor +1)
//fInvLambCorr*=((fInvLambCorr-1)*fLM_DP3LerpFactor +1);
fColorRLamb *= fInvLambCorr;
fColorGLamb *= fInvLambCorr;
fColorBLamb *= fInvLambCorr;
}
}
#ifdef REDUCE_DOT3LIGHTMAPS_TO_CLASSIC
fLM_DP3LerpFactor = 0.0f;
#endif
/*
// Fade out the bump when there's not enough illumination (spotlights)
Vec3d vDir = pVert->m_rvLightDir;
float fCos = __max(0.25f, vDir * pVert->m_vNormal);
pVert->m_fLM_DP3LerpFactor *= fCos;
*/
}
}
}
static void AddSpotLightTerm(float& fSpotlightTerm, const LMCompLight& rLight, const Vec3d& rvSamplePos, const Vec3d& vDir)
{
// ---------------------------------------------------------------------------------------------
// Spotlight term
// ---------------------------------------------------------------------------------------------
float fAccum = 1.0f;
if (rLight.eType == eSpotlight)
{
float fMaxAngle = sinf(rLight.fLightFrustumAngleDegree * gf_DEGTORAD);
float fInnerAngle = fMaxAngle / 2.33333f;
assert(fMaxAngle >= 0.0f);
assert(fInnerAngle <= fMaxAngle);
float fCos=-vDir * rLight.vDirection;
float fAngle = 1.0f - __max(0.0f, fCos);
if (fAngle > fMaxAngle)
fAccum = 0.0f;
else if (fAngle < fInnerAngle)
fAccum = 1.0f;
else
fAccum = 1.0f - (fAngle - fInnerAngle) / (fMaxAngle - fInnerAngle);
assert(fAccum >= 0.0f);
assert(fAccum <= 1.0f);
}
fSpotlightTerm += fAccum;
}
void CLightScene::WriteLogInfo()
{
FILE *pFile = fopen("Lightmap.log","w+");
if(pFile == NULL)
{
_TRACE(m_LogInfo, true, "\r\n Cannot write log-file! \r\n");
return;
}
FILE *pErrorFile = fopen("LightmapError.log","w+");
if(pErrorFile == NULL)
{
_TRACE(m_LogInfo, true, "\r\n Cannot write error-log-file! \r\n");
return;
}
_TRACE("For errors/warnings and more information see written log-file 'Lightmap.log' and 'LightmapError.log'\r\n", false);
for(std::vector<CString>::const_iterator iter = m_LogInfo.begin(); iter != m_LogInfo.end(); ++iter)
{
const CString rString = *iter;
fwrite( (const char*)rString,1,rString.GetLength(),pFile); //write encoded data
}
//now write warning summary
std::string sSummary = "-----ERROR/WARNING SUMMARY----------------------------------------------------------\r\n\r\n";
fwrite( sSummary.c_str(),1,sSummary.size(),pErrorFile); //write encoded data
if(m_WarningInfo.size() != 0)
{
unsigned int uiLastType = (m_WarningInfo.begin())->first;
for(std::multimap<unsigned int,std::string>::const_iterator iter = m_WarningInfo.begin(); iter != m_WarningInfo.end(); ++iter)
{
const unsigned int cuiCurrentType = iter->first;
if(cuiCurrentType != uiLastType)
{
fwrite("\r\n", 1, 2, pErrorFile);
uiLastType = cuiCurrentType;
}
fwrite( (iter->second).c_str(),1,(iter->second).size(),pErrorFile); //write encoded data
}
}
fclose(pFile); fclose(pErrorFile);
}
inline const float CLightScene::ComputeHalvedLightmapQuality(const float fOldValue)
{
const float cfGridSize = m_sParam.m_fTexelSize;
if(cfGridSize >= scfMaxGridSize)
return fOldValue;
if(fOldValue <= 1.f)
return (-(cfGridSize + 2*((scfMaxGridSize - cfGridSize) * (1.f - fOldValue)))/ (scfMaxGridSize - cfGridSize) + 1.0f);//compiler will optimize it on its own :)
if(fOldValue <= 2.f)
return 1.f;//coarse reset to 1, will decrease further if still not enough
return fOldValue * 0.5f;
}
void CLightScene::DoLightCalculation(
unsigned int &ruiMaxColourComp,
const std::vector<LMCompLight*>& vLights,
const std::vector<LMCompLight*>& vOcclLights,
SComplexOSDot3Texel& dot3Texel,
CRadMesh *pMesh,
CRadPoly *pSource,
const CRadVertex& Vert,
const bool cbFirstPass,
const unsigned int cuiSubSampleIndex)
{
Vec3d vLightDir = Vec3d(0, 0, 0);
float fColorRLamb = 0.0f, fColorGLamb = 0.0f, fColorBLamb = 0.0f;
float fLambLumSum=0.0f, fLumSum = 0.0f;
float fSpotlightTerm = 0.0f;
bool bApplyBetterLighting = false;
int uiLightCounter = -1;
for (std::vector<LMCompLight*>::const_iterator ligIt=vLights.begin(); ligIt<vLights.end(); ligIt++)
{
uiLightCounter++;
LMCompLight& cLight = *(*ligIt);
Vec3d vDir = -cLight.vDirection; //for directional light, but dont waste default constructor
if(cLight.eType != eDirectional)
{ //get vertex light direction if not directional
vDir = cLight.vWorldSpaceLightPos - Vert.m_vPos;
vDir.Normalize();
}
//retrieve light intensity according to type and position
float fIntens;
if(!GetLightIntensity(cLight, fIntens, Vert, vDir))
continue;
if (m_sParam.m_bComputeShadows)
{
UINT iNumHit = 0;
Shadow(cLight, iNumHit, pMesh, pSource, Vert, Vert.m_vPos);
if (iNumHit == 0) // Stop calculations if no light reaches the surface
continue;
}
//spotlight term
if(cLight.eType == eSpotlight && false == m_sParam.m_bSpotAsPointlight)
{
fSpotlightTerm = 0.0f;
AddSpotLightTerm(fSpotlightTerm, cLight, Vert.m_vPos, vDir);
fIntens *= fSpotlightTerm;
}
if (fIntens < 0.0f)
continue;
//alter dot3 values according to current light direction and colour
if(bApplyBetterLighting == false)//do only check whether at least one light was processed with the new lighting model
bApplyBetterLighting = SumDot3Values(cLight, fIntens, fLambLumSum, fLumSum, fColorRLamb, fColorGLamb, fColorBLamb, cLight, vLightDir, vDir, Vert);
else
SumDot3Values(cLight, fIntens, fLambLumSum, fLumSum, fColorRLamb, fColorGLamb, fColorBLamb, cLight, vLightDir, vDir, Vert);
if(cbFirstPass)
dot3Texel.uiLightMask |= (1 << (min(uiLightCounter,31)));//just make sure it can handle somehow more than 31 lights
} // ligit
//get dot3 values and add texel
float fLM_DP3LerpFactor;
GetDot3Sample(fLM_DP3LerpFactor,vLightDir,fLumSum,fLambLumSum,Vert,fColorRLamb,fColorGLamb,fColorBLamb, bApplyBetterLighting);
//now the occlusion map lights
SOcclusionMapTexel occlTexel;
if(m_sParam.m_bGenOcclMaps)
{
GLMOcclLightInfo& rOcclInfo = pMesh->m_OcclInfo;//cache occlusion light info
fSpotlightTerm = 0.f;
for (std::vector<LMCompLight*>::const_iterator ligIt=vOcclLights.begin(); ligIt<vOcclLights.end(); ligIt++)
{
uiLightCounter++;
LMCompLight& cLight = *(*ligIt);
Vec3d vDir = -cLight.vDirection; //for directional light, but dont waste default constructor
if(cLight.eType != eDirectional)
{ //get vertex light direction if not directional
vDir = cLight.vWorldSpaceLightPos - Vert.m_vPos;
vDir.Normalize();
}
//retrieve light intensity according to type and position
float fIntens;
if(!GetLightIntensity(cLight, fIntens, Vert, vDir))
continue;
UINT iNumHit = 0;
Shadow(cLight, iNumHit, pMesh, pSource, Vert, Vert.m_vPos);
if (iNumHit == 0) // Stop calculations if no light reaches the surface
continue;
//spotlight term
if(cLight.eType == eSpotlight && false == m_sParam.m_bSpotAsPointlight)
{
fSpotlightTerm = 0.0f;
AddSpotLightTerm(fSpotlightTerm, cLight, Vert.m_vPos, vDir);
fIntens *= fSpotlightTerm;
}
if (fIntens < 0.0f)
continue;
//else set it to visible
//fetch channel for this lightsource
EOCCLCOLOURASSIGNMENT eChannel = rOcclInfo.FindLightSource(cLight.m_CompLightID);
if(eChannel == EOCCLCOLOURASSIGNMENT_NONE)
{
//add light source, first time this light source has received a texel
eChannel = rOcclInfo.AddLightsource(cLight.m_CompLightID);
}
if(eChannel == EOCCLCOLOURASSIGNMENT_NONE)
{
//too many active occlusion map light sources for this glm
char text[scuiWarningTextAllocation];
sprintf(text, "GLM: %s has more than 4 active affecting occlusion lights\r\n",(const char*)pMesh->m_sGLMName);
m_WarningInfo.insert(std::pair<unsigned int, std::string>(EWARNING_TOO_MANY_OCCL_LIGHTS, std::string(text)));
}
occlTexel.SetValue(eChannel, 0xF);//mark as visible
if(cbFirstPass)
dot3Texel.uiLightMask |= (1 << (min(uiLightCounter,31)));//just make sure it can handle somehow more than 31 lights
} // ligit
}
if(cbFirstPass)
#ifdef APPLY_COLOUR_FIX
ruiMaxColourComp = __max(ruiMaxColourComp, pSource->SetDot3LightmapTexel(Vert, fColorRLamb, fColorGLamb, fColorBLamb, vLightDir, fLM_DP3LerpFactor, dot3Texel, occlTexel, m_sParam.m_bHDR));
#else
pSource->SetDot3LightmapTexel(Vert, fColorRLamb, fColorGLamb, fColorBLamb, vLightDir, fLM_DP3LerpFactor, dot3Texel, occlTexel, m_sParam.m_bHDR);
#endif
else
SetSubSampleDot3LightmapTexel(cuiSubSampleIndex, fColorRLamb, fColorGLamb, fColorBLamb, vLightDir, fLM_DP3LerpFactor, dot3Texel, occlTexel, m_sParam.m_bHDR);
}
bool CLightScene::Create( const IndoorBaseInterface &pInterface, const Vec3d& vMinBB, const Vec3d& vMaxBB, volatile SSharedLMEditorData *pSharedData, const ELMMode Mode, const unsigned int cuiMeshesToProcessCount)
{
//for logging stuff
static char sTraceString[1024];
// ---------------------------------------------------------------------------------------------
// Main lighting computation function
// ---------------------------------------------------------------------------------------------
UINT iCurMesh = 0;
_TRACE(m_LogInfo, true, "\r\nCalculating lighting... \r\n");
// ---------------------------------------------------------------------------------------------
// Check for lightsources
// ---------------------------------------------------------------------------------------------
memcpy(&m_IndInterface, &pInterface, sizeof(IndoorBaseInterface));
//initialize sample pattern
switch(m_sParam.m_iSubSampling)
{
case 1:
//already set by default
gAASettings.SetScale(1);
gAASettings.m_bEnabled = false;
break;
case 5:
gAASettings.m_bEnabled = true;
gAASettings.SetScale(2);
InitSubSampling(5);
break;
case 9:
gAASettings.m_bEnabled = true;
gAASettings.SetScale(3);
InitSubSampling(9);
break;
default:
//set to no AA by default
gAASettings.SetScale(1);
gAASettings.m_bEnabled = false;
break;
}
bool bSerialize = true;
unsigned char ucLastProgress = 101; //initialize to some number outside range 0..100
// Build individual meshes
bool bStarted = false;
//pointers to fetch everywhere
CRadMesh *pMesh;
IStatObj *pIGeom;
//display first GLM processing messages
unsigned int uiStartIndex = 0;
//seek to the first relevant GLM
radmeshit itRadMesh=m_lstRadMeshes.begin();
if(Mode != ELMMode_ALL)
{
for (;itRadMesh!=m_lstRadMeshes.end();itRadMesh++)
{
pMesh = (* itRadMesh);
if(pMesh == NULL || (pMesh->m_bReceiveLightmap == false))
{
uiStartIndex++;
continue; //does not receive any lightmaps, just casts shadow
}
break;
}
}
if(cuiMeshesToProcessCount > 0 && m_lstRadMeshes.size() > 0)
{
if(m_uiCurrentPatchNumber == 0)//allocate the space for the first lightmap
CreateNewLightmap();
pMesh = *itRadMesh;
if(pMesh && (pMesh->m_bReceiveLightmap == true))
{
pIGeom = pMesh->m_pESource->GetEntityStatObj(0, NULL);
int iNumIndices = 0;
pIGeom->GetLeafBuffer()->GetIndices(&iNumIndices);
sprintf(sTraceString, "Processing GLM No.1 with %i Tri and %i Light%c...\r\n",
iNumIndices / 3, pMesh->m_LocalLights.size(),
pMesh->m_LocalLights.size() == 1 ? ' ' : 's');
_TRACE(sTraceString);
sprintf(sTraceString, "Processing GLM No.1 ('%s' with brush type '%s') with %i Tri and %i Light%c...\r\n",
pMesh->m_sGLMName, pIGeom?pIGeom->GetFileName():"", iNumIndices / 3, pMesh->m_LocalLights.size(),
pMesh->m_LocalLights.size() == 1 ? ' ' : 's');
m_LogInfo.push_back(CString(sTraceString));
}
}
// basically iterate each receiving GLM
for (;itRadMesh!=m_lstRadMeshes.end();itRadMesh++)
{
pMesh = (* itRadMesh);
if(pMesh == NULL || (pMesh->m_bReceiveLightmap == false))
{
if(Mode == ELMMode_ALL)
iCurMesh++;//only count in case all GLMs are supposed to get processed
continue; //does not receive any lightmaps, just casts shadow
}
pIGeom = pMesh->m_pESource->GetEntityStatObj(0, NULL);
//alter some display things
if(pSharedData != NULL)
{
const unsigned char cucProgress = static_cast<unsigned int>(static_cast<float>(iCurMesh) / static_cast<float>(cuiMeshesToProcessCount + 1) * 100.0f);
if(cucProgress != ucLastProgress)
::SendMessage( pSharedData->hwnd, pSharedData->uiProgressMessage, cucProgress, 0 );//update progress bar
::SendMessage( pSharedData->hwnd, pSharedData->uiMemUsageMessage, min(GetUsedMemory(),1000), 0 );//update progress bar
::SendMessage( pSharedData->hwnd, pSharedData->uiMemUsageStatic, min(GetUsedMemory(),1000), 0 );//update progress bar static element
::SendMessage( pSharedData->hwnd, pSharedData->uiGLMNameEdit, (UINT_PTR)pMesh, (UINT_PTR)pIGeom );//update progress bar static element
ucLastProgress = cucProgress;
MSG msg;
while( FALSE != ::PeekMessage( &msg, 0, 0, 0, PM_REMOVE ) )
{
::TranslateMessage( &msg );
::DispatchMessage( &msg );
}
if(pSharedData->bCancelled == true)
{
bSerialize = false;
break;
}
}
bool bFound = false;
// Output progress (only if there was no overflow, don't show message again)
if (bStarted)
{
int iNumIndices = 0;
pIGeom->GetLeafBuffer()->GetIndices(&iNumIndices);
//different output here
sprintf(sTraceString, "Done with %i of %i GLMs. Processing GLM No.%i with %i Tri and %i Light%c...\r\n",
iCurMesh, cuiMeshesToProcessCount, iCurMesh+1, iNumIndices / 3, pMesh->m_LocalLights.size(),
pMesh->m_LocalLights.size() == 1 ? ' ' : 's');
_TRACE(sTraceString);
sprintf(sTraceString, "Done with %i of %i GLMs. Processing GLM No.%i ('%s' with brush type '%s') with %i Tri and %i Light%c...\r\n",
iCurMesh, cuiMeshesToProcessCount, iCurMesh+1, pMesh->m_sGLMName, pIGeom?pIGeom->GetFileName():"", iNumIndices / 3, pMesh->m_LocalLights.size(),
pMesh->m_LocalLights.size() == 1 ? ' ' : 's');
m_LogInfo.push_back(CString(sTraceString));
}
iCurMesh++;
// Prepare lightmap space for this mesh
pMesh->PrepareLightmaps(this, !m_sParam.m_bDontMergePolys, m_sParam.m_fTexelSize, vMinBB, vMaxBB);
// No lightmap to calculate
if (m_lstScenePolys.empty())
continue;
//set affecting lights for this patch
std::vector<LMCompLight*>& vLights = pMesh->m_LocalLights;
std::vector<LMCompLight*>& vOcclLights = pMesh->m_LocalOcclLights;
//check normals
if((pMesh->m_uiFlags & DOUBLE_SIDED_MAT) != 0)
{
_TRACE(m_LogInfo, true, "GLM No.%i has double sided materials where lightmaps can't get applied properly...\r\n", iCurMesh);
char text[scuiWarningTextAllocation];
sprintf(text, "GLM: %s (%s) has double sided materials where lightmaps can't get applied properly\r\n",(const char*)pMesh->m_sGLMName, pIGeom?pIGeom->GetFileName():"");
m_WarningInfo.insert(std::pair<unsigned int, std::string>(EWARNING_DOUBLE_SIDED, std::string(text)));
}
float fVariance;//to retrieve max variance within this GLM, artist will know how bad the normal export was
int retValue = 0;
if((retValue = CheckNormals(fVariance, pMesh)) > 0)
{
if(retValue & WRONG_NORMALS_FLAG)
{
_TRACE(m_LogInfo, true, "GLM No.%i has invalid normals, please do re-export, replacing normals...\r\n", iCurMesh);
char text[scuiWarningTextAllocation];
sprintf(text, "GLM: %s (%s) has invalid normals, please do re-export, normals replaced by default calculations\r\n",(const char*)pMesh->m_sGLMName, pIGeom?pIGeom->GetFileName():"");
m_WarningInfo.insert(std::pair<unsigned int, std::string>(EWARNING_WRONG_NORMALS, std::string(text)));
}
else
if(retValue & NOT_NORMALIZED_NORMALS_FLAG)
{
#ifdef DISPLAY_MORE_INFO
_TRACE(m_LogInfo, true, "GLM No.%i has denormalized normals, max variance to 1.0: %f, renormalizing...\r\n", iCurMesh, fVariance);
#else
_TRACE(m_LogInfo, false, "GLM No.%i has denormalized normals, max variance to 1.0: %f, renormalizing...\r\n", iCurMesh, fVariance);
#endif
char text[scuiWarningTextAllocation];
sprintf(text, "GLM: %s (%s) has denormalized normals, max variance to 1.0: %f, automatic renormalization\r\n",(const char*)pMesh->m_sGLMName, pIGeom?pIGeom->GetFileName():"", fVariance);
m_WarningInfo.insert(std::pair<unsigned int, std::string>(EWARNING_DENORM_NORMALS, std::string(text)));
}
}
//compute the smoothing group information for this GLM
const bool bTSGeneratedByLM = ((pMesh->m_uiFlags & WRONG_NORMALS_FLAG) == 0)?false:true;
ComputeSmoothingInformation(m_sParam.m_uiSmoothingAngle, bTSGeneratedByLM);
//iterate all individual lightmap patches in this receiving GLM
for (radpolyit i=m_lstScenePolys.begin(); i!=m_lstScenePolys.end(); i++)
{
CRadPoly *pSource = (* i);
if (pSource->m_dwFlags & NOLIGHTMAP_FLAG)
continue;
pSource->AllocateDot3Lightmap(m_sParam.m_bHDR, m_sParam.m_bGenOcclMaps);//for subsampling world space light vector is used
//iterate all texels of the current patch grid
#ifdef APPLY_COLOUR_FIX
unsigned int uiMaxColourComp = 0;
#endif
for(int y=0; y<pSource->m_nH; y++)
for(int x=0; x<pSource->m_nW; x++)
{
//choose one as texel center to get one texel smoothed at least
CRadVertex Vert;
//snap and smooth if no super sampling enabled
SComplexOSDot3Texel dot3Texel;
//snap middle point to make sure tangent space exists
if(!pSource->InterpolateVertexAt((float)x, (float)y, Vert, dot3Texel, false, true))//snap middle point to ensure hit
continue;
DoLightCalculation(uiMaxColourComp, vLights, vOcclLights, dot3Texel, pMesh, pSource, Vert, true);
} // x, y
//now perform adaptive subsampling
#ifdef APPLY_COLOUR_FIX
PerformAdaptiveSubSampling(pMesh, pSource, vLights, vOcclLights, uiMaxColourComp);
#else
PerformAdaptiveSubSampling(pMesh, pSource, vLights, vOcclLights);
#endif
//free some memory
pSource->m_SharingPolygons.clear();
pSource->m_SharingPolygons.resize(0);
} // i
// Create the lightmaps of this mesh
if (!pMesh->SaveLightmaps(this , m_sParam.m_bDebugBorders))
{
// Normal lightmap overflow, create new one
CreateNewLightmap();
//try it again
if (!pMesh->SaveLightmaps(this, m_sParam.m_bDebugBorders))
{
// If the function returns false, we ran out of lightmap space while processing the GLM.
// We already couldn't fit it in last time, object won't fit into our LM size at all
_TRACE(m_LogInfo, true, "GLM No. %i cannot fit into lightmap size %ix%i, recomputing with lower resolution...\r\n",
iCurMesh, m_sParam.m_iTextureResolution, m_sParam.m_iTextureResolution);
//now change nLightmapQuality to not let this happen again
if(pMesh->pLightmapQualityVar)
{
float fCurrentValue = 1.f; float fOldValue = 1.f;
pMesh->pLightmapQualityVar->Get(fOldValue);
if(fCurrentValue > 0.f)//if not already reached maximum grid size
{
fCurrentValue = ComputeHalvedLightmapQuality(fOldValue);//halve resolution
pMesh->pLightmapQualityVar->Set(fCurrentValue);
pMesh->m_fLMAccuracy = fCurrentValue;
_TRACE("...setting new value for nLightmapQuality to: %f \r\n",fCurrentValue);
}
char text[scuiWarningTextAllocation];
sprintf(text, "GLM: %s did not fit into a single lightmap, nLightmapQuality has been changed from %f to %f\r\n",(const char*)pMesh->m_sGLMName, fOldValue, fCurrentValue);
m_WarningInfo.insert(std::pair<unsigned int, std::string>(EWARNING_NO_FIT, std::string(text)));
}
assert(m_pCurrentPatch != NULL);
m_pCurrentPatch->Reset();
itRadMesh--;
iCurMesh--;
continue;
}
}
assert(m_pCurrentPatch != NULL);
// ---------------------------------------------------------------------------------------------
// Texture coordinates
// ---------------------------------------------------------------------------------------------
LMAssignQueueItem sNewGLMQueueItm;
//set occlusion id's
for(int i = 0; i<pMesh->m_OcclInfo.uiLightCount;i++)
sNewGLMQueueItm.vOcclIDs.push_back(pMesh->m_OcclInfo.iLightIDs[i]);
GenerateTexCoords(*pMesh, sNewGLMQueueItm);
// Add the object to the assign queue for this light patch, we will create the
// RenderLMData object and assigne it to the GLMs when the patch is filled and we are about to create a new one
m_lstAssignQueue.push_back(sNewGLMQueueItm);
//remove reference to rastercube
m_RasterCubeManager.RemoveReference(pMesh);
//free patch
delete pMesh; *itRadMesh = pMesh = NULL;
bStarted = true;
} // itRadMesh
m_lstRadMeshes.clear();
//signal completion
if(pSharedData != NULL)
{
const unsigned char cuiProgress = (bSerialize == true)?static_cast<unsigned int>(static_cast<float>(cuiMeshesToProcessCount) / static_cast<float>(cuiMeshesToProcessCount + 1) * 100.0f):0;
::SendMessage( pSharedData->hwnd, pSharedData->uiProgressMessage, cuiProgress, 0 );
::SendMessage( pSharedData->hwnd, pSharedData->uiMemUsageMessage, min(GetUsedMemory(),1000), 0 );//update progress bar
::SendMessage( pSharedData->hwnd, pSharedData->uiMemUsageStatic, min(GetUsedMemory(),1000), 0 );//update progress bar static element
::SendMessage( pSharedData->hwnd, pSharedData->uiGLMNameEdit, 0, 0 );//update progress bar static element
MSG msg;
while( FALSE != ::PeekMessage( &msg, 0, 0, 0, PM_REMOVE ) )
{
TranslateMessage( &msg );
DispatchMessage( &msg );
}
}
//leave it as it is if user has pressed cancel or close
if(bSerialize == false)
return true;
return FlushAndSave(pSharedData, Mode);
}
void CLightScene::GenerateTexCoords(const CRadMesh& rRadMesh, LMAssignQueueItem& rNewGLMQueueItm)
{
rNewGLMQueueItm.pI_GLM = rRadMesh.m_pESource;
rNewGLMQueueItm.m_dwHashValue=rRadMesh.GetHashValue();
assert(rRadMesh.m_lstOldPolys.size() != 0);
rNewGLMQueueItm.vSortedTexCoords.resize(rRadMesh.m_uiTexCoordsRequired);
//get leafbuffer to write at the right pos
CLeafBuffer *pLB = (rRadMesh.m_pESource->GetEntityStatObj(0, NULL))->GetLeafBuffer();
assert(!pLB->IsEmpty());
int iIdxCnt = 0;
unsigned short *pIndices = pLB->GetIndices(&iIdxCnt);
UINT iCurMat;
list2<CMatInfo> *pMaterials = pLB->m_pMats;
std::vector<CRadPoly *>::const_iterator itPoly = rRadMesh.m_lstOldPolys.begin();
for (iCurMat=0; iCurMat<pMaterials->Count(); iCurMat++)
{
CMatInfo cCurMat = (* pMaterials)[iCurMat];
// Check if the material is opaque, we don't let non-opaque geometry cast shadows
IShader *pEff = cCurMat.shaderItem.m_pShader;
if (pEff)
{
IShader *pShTempl = pEff->GetTemplate(-1);
if (pShTempl)
{
if(pShTempl->GetFlags3() & EF3_NODRAW)
continue;
}
}
// Process all triangles
UINT iCurTri = 0;
for (iCurTri=0; iCurTri<cCurMat.nNumIndices / 3; iCurTri++)
{
const unsigned int iBaseIdx = cCurMat.nFirstIndexId + iCurTri * 3;
CRadPoly *pPoly = (* itPoly);
assert(pPoly->m_lstOriginals.size() == 3);
rNewGLMQueueItm.vSortedTexCoords[pIndices[iBaseIdx]] =
(TexCoord2Comp(pPoly->m_lstOriginals[2].m_fpX, pPoly->m_lstOriginals[2].m_fpY));
rNewGLMQueueItm.vSortedTexCoords[pIndices[iBaseIdx+1]] =
(TexCoord2Comp(pPoly->m_lstOriginals[1].m_fpX, pPoly->m_lstOriginals[1].m_fpY));
rNewGLMQueueItm.vSortedTexCoords[pIndices[iBaseIdx+2]] =
(TexCoord2Comp(pPoly->m_lstOriginals[0].m_fpX, pPoly->m_lstOriginals[0].m_fpY));
itPoly++;
}
}
}
const bool CLightScene::FlushAndSave(volatile SSharedLMEditorData *pSharedData, const ELMMode Mode)
{
// Be sure we assign all GLMs a lightmap object. Function may return false here in case
// we already assigned all or there was no more data to process
FlushAssignQueue();
UINT iNumBytes = m_uiCurrentPatchNumber * m_sParam.m_iTextureResolution * m_sParam.m_iTextureResolution * (4 + 4);
if(Mode != ELMMode_ALL)
_TRACE(m_LogInfo, true, "Created a total of %i lightmap texture pairs (R8G8B8A8 + R8G8B8A8), ~%3.2f MB of additional lightmap texture data\r\n",
m_uiCurrentPatchNumber, iNumBytes / 1024.0f / 1024.0f);
else
_TRACE(m_LogInfo, true, "Created a total of %i lightmap texture pairs (R8G8B8A8 + R8G8B8A8), ~%3.2f MB of lightmap texture data\r\n",
m_uiCurrentPatchNumber, iNumBytes / 1024.0f / 1024.0f);
// Export all LM data to LM_EXPORT_FILE_NAME in the directory of the level
string strLMExportFile = m_IndInterface.m_p3dEngine->GetFilePath(LM_EXPORT_FILE_NAME);
_TRACE(m_LogInfo, true, "Exporting lightmap data to '%s'\r\n", strLMExportFile.c_str());
unsigned int res = m_pISerializationManager->Save(strLMExportFile.c_str(), m_sParam, Mode != ELMMode_ALL);
switch(res)
{
case NSAVE_RESULT::ESUCCESS:
break;
case NSAVE_RESULT::EPAK_FILE_UPDATE_FAIL:
_TRACE(m_LogInfo, true, "Could not update lightmap pak-file. Exporting failed!\r\n");
m_WarningInfo.insert(std::pair<unsigned int, std::string>(EWARNING_EXPORT_FAILED, std::string("Could not update lightmap pak-file. Exporting failed!\r\n")));
return false;
case NSAVE_RESULT::EDXT_COMPRESS_FAIL:
_TRACE(m_LogInfo, true, "DXT compression for lightmaps failed. Exporting failed!\r\n");
m_WarningInfo.insert(std::pair<unsigned int, std::string>(EWARNING_EXPORT_FAILED, std::string("DXT compression for lightmaps failed. Exporting failed!\r\n")));
return false;
case NSAVE_RESULT::EPAK_FILE_OPEN_FAIL:
_TRACE(m_LogInfo, true, "Could not open lightmap pak-file. Exporting failed!\r\n");
m_WarningInfo.insert(std::pair<unsigned int, std::string>(EWARNING_EXPORT_FAILED, std::string("Could not open lightmap pak-file. Exporting failed!\r\n")));
return false;
default:
_TRACE(m_LogInfo, true, "Exporting failed!\r\n");
m_WarningInfo.insert(std::pair<unsigned int, std::string>(EWARNING_EXPORT_FAILED, std::string("Exporting of lightmaps failed!\r\n")));
return false;
}
//signal completion
if(pSharedData != NULL)
{
::SendMessage( pSharedData->hwnd, pSharedData->uiProgressMessage, 100, 0 );
::SendMessage( pSharedData->hwnd, pSharedData->uiMemUsageMessage, min(GetUsedMemory(),1000), 0 );//update progress bar
::SendMessage( pSharedData->hwnd, pSharedData->uiMemUsageStatic, min(GetUsedMemory(),1000), 0 );//update progress bar static element
MSG msg;
while( FALSE != ::PeekMessage( &msg, 0, 0, 0, PM_REMOVE ) )
{
::TranslateMessage( &msg );
::DispatchMessage( &msg );
}
}
return true;
}
void CLightScene::FlushAssignQueue()
{
// ---------------------------------------------------------------------------------------------
// Create a lightmap from the current patch (m_pCurrentPatch) and assign the shared lightmaps
// as well as instance specific texture coordinates to all GLMs in the assign queue
// (m_lstAssignQueue)
// ---------------------------------------------------------------------------------------------
std::list<LMAssignQueueItem>::iterator itItem;
if (m_pCurrentPatch == NULL)
return ;
if (m_lstAssignQueue.empty())
return ;
_TRACE(m_LogInfo, true, "%ix%i lightmap filled with %i objects, flushing\r\n",
m_sParam.m_iTextureResolution, m_sParam.m_iTextureResolution, m_lstAssignQueue.size());
std::vector<int> vGLM_IDs_Using;
for (itItem=m_lstAssignQueue.begin(); itItem!=m_lstAssignQueue.end(); itItem++)
{
vGLM_IDs_Using.push_back((* itItem).pI_GLM->GetEditorObjectId());
}
m_pISerializationManager->AddRawLMData(
m_sParam.m_iTextureResolution, m_sParam.m_iTextureResolution, vGLM_IDs_Using,
m_pCurrentPatch->m_pLightmapImage, m_pCurrentPatch->m_pHDRLightmapImage, m_pCurrentPatch->m_pDominantDirImage, m_pCurrentPatch->m_pOcclMapImage?reinterpret_cast<BYTE*>(&(m_pCurrentPatch->m_pOcclMapImage[0].colour)):0);
// Create a new lightmap
RenderLMData_AutoPtr pNewLM = m_pISerializationManager->CreateLightmap
(NULL, 0, m_sParam.m_iTextureResolution, m_sParam.m_iTextureResolution,
m_pCurrentPatch->m_pLightmapImage, m_pCurrentPatch->m_pHDRLightmapImage, m_pCurrentPatch->m_pDominantDirImage, m_pCurrentPatch->m_pOcclMapImage?reinterpret_cast<BYTE*>(&(m_pCurrentPatch->m_pOcclMapImage[0].colour)):0);
for (itItem=m_lstAssignQueue.begin(); itItem!=m_lstAssignQueue.end(); itItem++)
{
LMAssignQueueItem& rCurItm = *itItem;
// Pass a reference of the lightmap and texture coordinates
if(strcmp(rCurItm.pI_GLM->GetEntityClassName(), "Brush") == 0)
{
std::vector<std::pair<EntityId, EntityId> > IDs;
IDs.resize(4);
for(int i=0; i<4; i++)
IDs[i] = std::pair<EntityId, EntityId>(0,0);
assert(rCurItm.vOcclIDs.size()<=4);
for(int i=0; i<rCurItm.vOcclIDs.size(); ++i)
IDs[i] = rCurItm.vOcclIDs[i];
rCurItm.pI_GLM->SetLightmap(pNewLM, (float*)&rCurItm.vSortedTexCoords[0], rCurItm.vSortedTexCoords.size(), rCurItm.vOcclIDs.size(), IDs);
}
else
rCurItm.pI_GLM->SetLightmap(pNewLM, (float*)&rCurItm.vSortedTexCoords[0], rCurItm.vSortedTexCoords.size());
// GLMs are mapped to texture coordinate data by position
m_pISerializationManager->AddTexCoordData(rCurItm.vSortedTexCoords, rCurItm.pI_GLM->GetEditorObjectId(),rCurItm.m_dwHashValue, rCurItm.vOcclIDs);
}
// Empty queue
m_lstAssignQueue.clear();
pNewLM = NULL;
delete m_pCurrentPatch; m_pCurrentPatch = NULL;
}
void CLightScene::GatherSubSampleTexel(const CRadPoly *pSource, const int ciX, const int ciY, std::set<std::pair<unsigned int, unsigned int> >& rvSubTexels)
{
//if triangle was not hit properly(needed a snap), subsample
const SComplexOSDot3Texel *pWSDominantDirData = pSource->m_pWSDominantDirData;
const unsigned int cuiPatchWidth = pSource->m_nW;
const unsigned int cuiPatchHeight = pSource->m_nH;
const SComplexOSDot3Texel& rDot3Texel = pWSDominantDirData[ciY*cuiPatchWidth+ciX];
const unsigned char *pTexelColour = &(pSource->m_pLightmapData[4*(ciY * cuiPatchWidth + ciX)]);
const unsigned char *pHDRTexelColour = NULL;
static const unsigned int scuiMaxDiff = 30; //default coarse max value
static const float scfMaxDot3Diff = 0.86f; //default coarse max value cosine for dot product check
static const unsigned int scuiNotHitMaxDiff = 17; //finer value if a texel has not been hit the patch
static const float scfNotHitMaxDot3Diff = 0.9f; //finer value if a texel has not been hit the patch
bool bVertexInserted = false;
//check all neighbour texels
for(int u = max(ciX-1,0); u <= min(ciX+1,cuiPatchWidth-1); u++)
for(int v = max(ciY-1,0); v <= min(ciY+1,cuiPatchHeight-1); v++)
{
if(u == ciX && v == ciY)
continue;//do not check with itself
//subsample if not hit this patch or if lightmask is different
const SComplexOSDot3Texel& rDot3NeighbourTexel = pWSDominantDirData[v*cuiPatchWidth+u];
if(rDot3Texel.uiLightMask != rDot3NeighbourTexel.uiLightMask)
{
if(!bVertexInserted)
{
bVertexInserted = true;
rvSubTexels.insert(std::pair<unsigned int, unsigned int>(ciX, ciY));
return;
}
}
const bool cbNotHitCond = (rDot3NeighbourTexel.bNotHit | rDot3Texel.bNotHit);//set to true if any texel has not been hit
//now check for colour diff
const unsigned char *pNeighbourTexelColour = &(pSource->m_pLightmapData[4*(v * cuiPatchWidth + u)]);
for(int i=0; i<3; i++)
{
if(abs(pNeighbourTexelColour[i] - pTexelColour[i]) > (cbNotHitCond?scuiNotHitMaxDiff:scuiMaxDiff))
{
if(!bVertexInserted)
{
bVertexInserted = true;
rvSubTexels.insert(std::pair<unsigned int, unsigned int>(ciX, ciY));
return;
}
}
}
//check whether any light has hit the surface for both texels
if(rDot3NeighbourTexel.pSourcePatch == NULL && rDot3Texel.pSourcePatch == NULL)
continue;
if(rDot3NeighbourTexel.vDot3Light.GetLengthSquared() < 0.5f && rDot3Texel.vDot3Light.GetLengthSquared() < 0.5f)
continue;//no light has hit the surface for both texels
//now check dot3 vector diff
if(rDot3NeighbourTexel.vDot3Light * rDot3Texel.vDot3Light < (cbNotHitCond?scfNotHitMaxDot3Diff:scfMaxDot3Diff))
{
if(!bVertexInserted)
{
bVertexInserted = true;
rvSubTexels.insert(std::pair<unsigned int, unsigned int>(ciX, ciY));
return;
}
}
}
}
void CLightScene::SetSubSampleDot3LightmapTexel(
const unsigned int cuiSubSampleIndex,
const float fColorRLamb, const float fColorGLamb, const float fColorBLamb,
Vec3d &inLightDir, const float cfLM_DP3LerpFactor,
SComplexOSDot3Texel& rDot3Texel, const SOcclusionMapTexel& rOcclTexel, bool bHDR)
{
m_pfColours[4*cuiSubSampleIndex] = fColorRLamb;
m_pfColours[4*cuiSubSampleIndex+1] = fColorGLamb;
m_pfColours[4*cuiSubSampleIndex+2] = fColorBLamb;
m_pfColours[4*cuiSubSampleIndex+3] = cfLM_DP3LerpFactor;
rDot3Texel.vDot3Light = inLightDir;
m_pSubSamples[cuiSubSampleIndex] = rDot3Texel;
if(m_pOcclSamples)
m_pOcclSamples[cuiSubSampleIndex] = rOcclTexel;
}
#ifdef APPLY_COLOUR_FIX
void CLightScene::PerformAdaptiveSubSampling(CRadMesh *pMesh, CRadPoly *pSource, const std::vector<LMCompLight*>& vLights, const std::vector<LMCompLight*>& vOcclLights, unsigned int uiMaxComponent)
#else
void CLightScene::PerformAdaptiveSubSampling(CRadMesh *pMesh, CRadPoly *pSource, const std::vector<LMCompLight*>& vLights, const std::vector<LMCompLight*>& vOcclLights)
#endif
{
/* idea: go through the patch and mark the texels who need some more accurate sampling:
1. if lightmask of any neightbour texel is different
2. if triangle source ID is different
after this, downsample respective points, allocate dot3lightmap, synchronize it, free unused WSlightmap and set tangent space texel
center texel which is already computed will get ignored but used to the GatherSubSamples - routine
*/
if(m_uiSampleCount > 1)//perform subsampling only if requested
{
assert(m_puiIndicator); //enough to ensure others are valid as well
//now check which texel an accurater sampling scheme need
std::set<std::pair<unsigned int, unsigned int> > vSubTexels; //set containing all (unique) texel indices requiring subsampling
//always check the neighbour texels for any differences
for(int y=0; y<pSource->m_nH; y++)//supersample
for(int x=0; x<pSource->m_nW; x++)//supersample
GatherSubSampleTexel(pSource, x, y, vSubTexels);
if(vSubTexels.size() > 1)
pSource->m_dwFlags |= DO_NOT_COMPRESS_FLAG;//optimization
unsigned int uiMaxColourComp = 0; //not needed here but to be able to share a function
//now subsample for all these texels
for(std::set<std::pair<unsigned int, unsigned int> >::const_iterator iter = vSubTexels.begin(); iter != vSubTexels.end(); ++iter)
{
int uiSubSampleIndex = -1;//index to access subsample storage arrays
memset(m_puiIndicator, 0, m_uiSampleCount * sizeof(unsigned int)); // clear indicator
const unsigned int cuiX = iter->first; const unsigned int cuiY = iter->second;
for(int y=cuiY*gAASettings.GetScale(); y<(cuiY+1)*gAASettings.GetScale(); y++)//supersample
for(int x=cuiX*gAASettings.GetScale(); x<(cuiX+1)*gAASettings.GetScale(); x++)//supersample
{
if(gAASettings.IsMiddle(x%gAASettings.GetScale(),y%gAASettings.GetScale()))
continue;//center texel has already been computed
uiSubSampleIndex++;
//choose one as texel center to get one texel smoothed at least
CRadVertex Vert;
SComplexOSDot3Texel dot3Texel;
//snap middle point to make sure tangent space exists
if(!pSource->InterpolateVertexAt((float)x, (float)y, Vert, dot3Texel, true, false))//do subsample and do not snap
continue;
m_puiIndicator[uiSubSampleIndex] = 1;
DoLightCalculation(uiMaxColourComp, vLights, vOcclLights, dot3Texel, pMesh, pSource, Vert, false, uiSubSampleIndex);
} // x, y
//apply subsamples
#ifdef APPLY_COLOUR_FIX
pSource->GatherSubSamples(cuiX, cuiY, uiSubSampleIndex+1, m_puiIndicator, m_pfColours, m_pSubSamples, uiMaxComponent, m_pOcclSamples, pMesh->m_OcclInfo);
#else
pSource->GatherSubSamples(cuiX, cuiY, uiSubSampleIndex+1, m_puiIndicator, m_pfColours, m_pSubSamples, m_pOcclSamples, pMesh->m_OcclInfo);
#endif
}
}
//now allocate dot3lightmap, synchronize it, free unused WSlightmap and set tangent space texel
assert(pSource->m_pDominantDirData == NULL);//shouldnt yet be allocated
#ifdef USE_DOT3_ALPHA
pSource->m_pDominantDirData = new unsigned char [pSource->m_nW*pSource->m_nH*4]; assert(pSource->m_pDominantDirData);
//bear in mind that the alpha channel will only remain 0 for non set texels to signal not to include it for a possible mipmap generation for low spec
memset(pSource->m_pDominantDirData,0,pSource->m_nW*pSource->m_nH*4); //clear the image
#else
pSource->m_pDominantDirData = new unsigned char [pSource->m_nW*pSource->m_nH*3]; assert(pSource->m_pDominantDirData);
memset(pSource->m_pDominantDirData,0,pSource->m_nW*pSource->m_nH*3); //clear the image
#endif
#ifdef APPLY_COLOUR_FIX
const float cfColourScale = (uiMaxComponent > 3)?255.f / (float)uiMaxComponent : 1.f;//prevent any zero calculation
const unsigned int cuiColourFixAlpha = (const unsigned char)min(255.0f, 128.f / cfColourScale);
#endif
//now set the tangent space values
for(int y=0; y<pSource->m_nH; y++)
for(int x=0; x<pSource->m_nW; x++)
{
#ifdef APPLY_COLOUR_FIX
pSource->SetDot3TSLightmapTexel(x, y, cuiColourFixAlpha, cfColourScale);
#else
pSource->SetDot3TSLightmapTexel(x, y);
#endif
}
//free and synchronize resources
delete [] pSource->m_pWSDominantDirData; pSource->m_pWSDominantDirData = NULL;
// pSource->SynchronizeLightmaps();
}
void CLightScene::CreateNewLightmap()
{
// ---------------------------------------------------------------------------------------------
// Create space for a new lightmap
// ---------------------------------------------------------------------------------------------
// Make sure we assign all previous GLMs that used
// the current patch before we start a new one
FlushAssignQueue();
_TRACE(m_LogInfo, true, "New lightmap %ix%i created\r\n", m_sParam.m_iTextureResolution, m_sParam.m_iTextureResolution);
m_uiCurrentPatchNumber++;
// Create a new patch
m_pCurrentPatch = new CLightPatch(m_sParam.m_iTextureResolution);
m_pCurrentPatch->CreateDot3Lightmap(m_sParam.m_iTextureResolution, m_sParam.m_iTextureResolution,m_sParam.m_bHDR, m_sParam.m_bGenOcclMaps);
m_pCurrentPatch->m_nTextureNum = m_uiCurrentPatchNumber;
}