// --------------------------------------------------------------------------------------------- // 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 #include #include #include "I3dEngine.h" #include #include "IndoorLightPatches.h" #include "IEntityRenderstate.h" #include 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 >::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 >(pImpl, std::set())); return pImpl; } const bool CRasterCubeManager::RemoveReference(CRadMesh* cpRadMesh) { if(cpRadMesh->m_pClusterRC == NULL) return false; //there has never been a reference added std::map >::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 >::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 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(&rPolyInner, uiSecondOuter)); //add this polygon to the respective list and vice versa rPolyInner.m_SharingPolygons.push_back(std::pair(&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(&rPolyInner, uiSecondOuter)); //add this polygon to the respective list and vice versa rPolyInner.m_SharingPolygons.push_back(std::pair(&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& 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::const_iterator itGeom; iNumTri = 0; for (std::set::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::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::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 (pLB->GetPosPtr(iVtxStride)); // Loop through all materials UINT iCurMat; list2 *pMaterials = pLB->m_pMats; for (iCurMat=0; iCurMatCount(); 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 (&(reinterpret_cast(pVertices)[pIndices[iBaseIdx + 0] * iVtxStride]))); vWorldSpaceTri[1] = matEtyMtx.TransformPointOLD (* reinterpret_cast (&(reinterpret_cast(pVertices)[pIndices[iBaseIdx + 1] * iVtxStride]))); vWorldSpaceTri[2] = matEtyMtx.TransformPointOLD (* reinterpret_cast (&(reinterpret_cast(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 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 >&vNodes, const std::vector& vAABBs, const Matrix33& rMatSunBasis, const std::vector& rvNodeRadMeshIndices) { //find out all objects which need to be detected due to receiving shadows from hash changed objects int iMeshCounter = -1; for (std::list::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 vSVs; //shadow volumes //prepare shadow volumes bool bHasSunLight = false; for(std::vector::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::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::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 >& vNodes, const std::vector& vAABBs, const Matrix33& rMatSunBasis, const std::vector& 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::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 vSVs; //shadow volumes //prepare shadow volumes bool bHasSunLight = false; for(std::vector::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::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::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::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(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(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(EWARNING_LIGHT_FRUSTUM, std::string(text))); } } } bool CLightScene::CreateFromEntity(const IndoorBaseInterface &pInterface, LMGenParam sParam, std::vector >& vNodes, CLMLightCollection& cLights, ICompilerProgress *pIProgress, const ELMMode Mode, volatile SSharedLMEditorData *pSharedData, const std::set& vSelectionIndices, bool &rErrorsOccured) { m_uiStartMemoryConsumption = 0; //new run rErrorsOccured = false; // --------------------------------------------------------------------------------------------- // Compute LMs for vNodes // --------------------------------------------------------------------------------------------- Init(); std::vector >::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::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(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 vAABBs; vAABBs.reserve(vNodes.size()); std::vector 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::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::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::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 vSVs; //shadow volumes AABB aabbCluster((* itCluster).vMin, (* itCluster).vMax); // Find all light sources which overlap with this cluster for (std::vector::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::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::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::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::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& vLights, const std::vector& 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::const_iterator ligIt=vLights.begin(); ligItm_OcclInfo;//cache occlusion light info fSpotlightTerm = 0.f; for (std::vector::const_iterator ligIt=vOcclLights.begin(); ligItm_sGLMName); m_WarningInfo.insert(std::pair(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(static_cast(iCurMesh) / static_cast(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& vLights = pMesh->m_LocalLights; std::vector& 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(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(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(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; ym_nH; y++) for(int x=0; xm_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(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; im_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(static_cast(cuiMeshesToProcessCount) / static_cast(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 *pMaterials = pLB->m_pMats; std::vector::const_iterator itPoly = rRadMesh.m_lstOldPolys.begin(); for (iCurMat=0; iCurMatCount(); 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; iCurTrim_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(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(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(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(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::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 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(&(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(&(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 > IDs; IDs.resize(4); for(int i=0; i<4; i++) IDs[i] = std::pair(0,0); assert(rCurItm.vOcclIDs.size()<=4); for(int i=0; iSetLightmap(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 >& 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(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(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(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& vLights, const std::vector& vOcclLights, unsigned int uiMaxComponent) #else void CLightScene::PerformAdaptiveSubSampling(CRadMesh *pMesh, CRadPoly *pSource, const std::vector& vLights, const std::vector& 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 > vSubTexels; //set containing all (unique) texel indices requiring subsampling //always check the neighbour texels for any differences for(int y=0; ym_nH; y++)//supersample for(int x=0; xm_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 >::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; ym_nH; y++) for(int x=0; xm_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; }