// NeL - MMORPG Framework
// Copyright (C) 2010 Winch Gate Property Limited
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
#include "std3d.h"
#include "nel/3d/instance_lighter.h"
#include "nel/3d/mesh_multi_lod.h"
#include "nel/misc/file.h"
#include "nel/misc/path.h"
#include "nel/3d/visual_collision_manager.h"
#include "nel/3d/visual_collision_entity.h"
#include "nel/3d/ig_surface_light_build.h"
using namespace std;
using namespace NLMISC;
namespace NL3D {
// Bad coded: don't set too big else it allocates too much memory.
#define NL3D_INSTANCE_LIGHTER_CUBE_GRID_SIZE 16
// ***************************************************************************
// ***************************************************************************
// Setup part
// ***************************************************************************
// ***************************************************************************
// ***************************************************************************
CInstanceLighter::CLightDesc::CLightDesc ()
{
LightDirection.set (1, 1, -1);
GridSize=512;
GridCellSize=4;
Shadow= true;
OverSampling= 0;
DisableSunContribution= false;
}
// ***************************************************************************
CInstanceLighter::CInstanceLighter()
{
_IGSurfaceLightBuild= NULL;
}
// ***************************************************************************
void CInstanceLighter::init ()
{
}
// ***************************************************************************
void CInstanceLighter::addTriangles (CLandscape &landscape, std::vector &listZone, uint order, std::vector& triangleArray)
{
// Lamed from CZoneLighter.
// Set all to refine
excludeAllPatchFromRefineAll (landscape, listZone, false);
// Setup the landscape
landscape.setThreshold (0);
landscape.setTileMaxSubdivision (order);
// Refine it
landscape.refineAll (CVector (0, 0, 0));
// Dump tesselated triangles
std::vector leaves;
landscape.getTessellationLeaves(leaves);
// Number of leaves
uint leavesCount=leaves.size();
// Reserve the array
triangleArray.reserve (triangleArray.size()+leavesCount);
// Scan each leaves
for (uint leave=0; leaveVBase->EndPos, face->VLeft->EndPos, face->VRight->EndPos), -1 ));
}
// Setup the landscape
landscape.setThreshold (1000);
landscape.setTileMaxSubdivision (0);
// Remove all triangles
landscape.refineAll (CVector (0, 0, 0));
landscape.refineAll (CVector (0, 0, 0));
landscape.refineAll (CVector (0, 0, 0));
landscape.refineAll (CVector (0, 0, 0));
landscape.refineAll (CVector (0, 0, 0));
landscape.refineAll (CVector (0, 0, 0));
landscape.refineAll (CVector (0, 0, 0));
landscape.refineAll (CVector (0, 0, 0));
landscape.refineAll (CVector (0, 0, 0));
landscape.refineAll (CVector (0, 0, 0));
}
// ***************************************************************************
void CInstanceLighter::addTriangles (const IShape &shape, const NLMISC::CMatrix& modelMT, std::vector& triangleArray, sint instanceId)
{
// Lamed from CZoneLighter.
// Cast to CMesh
const CMesh *mesh=dynamic_cast(&shape);
// Cast to CMeshMultiLod
const CMeshMultiLod *meshMulti=dynamic_cast(&shape);
// Cast to CMeshMultiLod
const CMeshMRM *meshMRM=dynamic_cast(&shape);
// It is a mesh ?
if (mesh)
{
// Add its triangles
addTriangles (mesh->getMeshGeom (), modelMT, triangleArray, instanceId);
}
// It is a CMeshMultiLod ?
else if (meshMulti)
{
// Get the first geommesh
const IMeshGeom *meshGeom=&meshMulti->getMeshGeom (0);
// Dynamic cast
const CMeshGeom *geomMesh=dynamic_cast(meshGeom);
if (geomMesh)
{
addTriangles (*geomMesh, modelMT, triangleArray, instanceId);
}
// Dynamic cast
const CMeshMRMGeom *mrmGeomMesh=dynamic_cast(meshGeom);
if (mrmGeomMesh)
{
addTriangles (*mrmGeomMesh, modelMT, triangleArray, instanceId);
}
}
// It is a CMeshMultiLod ?
else if (meshMRM)
{
// Get the first lod mesh geom
addTriangles (meshMRM->getMeshGeom (), modelMT, triangleArray, instanceId);
}
}
// ***************************************************************************
void CInstanceLighter::addTriangles (const CMeshGeom &meshGeom, const CMatrix& modelMT, std::vector& triangleArray, sint instanceId)
{
// Get the vertex buffer
const CVertexBuffer &vb=meshGeom.getVertexBuffer();
CVertexBufferRead vba;
vb.lock (vba);
// For each matrix block
uint numBlock=meshGeom.getNbMatrixBlock();
for (uint block=0; block& triangleArray, sint instanceId)
{
// Get the vertex buffer
const CVertexBuffer &vb=meshGeom.getVertexBuffer();
CVertexBufferRead vba;
vb.lock (vba);
// For each render pass
uint numRenderPass=meshGeom.getNbRdrPass(0);
for (uint pass=0; pass &listZone, bool exclude)
{
// For each zone
for (uint zone=0; zonegetNumPatchs();
// For each patches
for (uint patch=0; patch& obstacles, CLandscape *landscape, CIGSurfaceLightBuild *igSurfaceLightBuild)
{
sint i;
CVector outGlobalPos;
std::vector outClusters;
std::vector outPortals;
std::vector pointLightList;
nlassert(lightDesc.OverSampling==0 || lightDesc.OverSampling==2 || lightDesc.OverSampling==4
|| lightDesc.OverSampling==8 || lightDesc.OverSampling==16);
// Setup.
//========
// Prepare IGSurfaceLight lighting
//-----------
// Bkup SurfaceLightBuild to know if must light the surfaces, in differents part of the process.
_IGSurfaceLightBuild= igSurfaceLightBuild;
// Prepare _IGRetrieverGridMap.
_IGRetrieverGridMap.clear();
if(_IGSurfaceLightBuild)
{
_TotalCellNumber= 0;
CIGSurfaceLightBuild::ItRetrieverGridMap itSrc;
itSrc= _IGSurfaceLightBuild->RetrieverGridMap.begin();
// For all retrievers Infos in _IGSurfaceLightBuild
while(itSrc!=_IGSurfaceLightBuild->RetrieverGridMap.end())
{
uint numSurfaces= itSrc->second.Grids.size();
// If !empty retriever.
if(numSurfaces>0)
{
// Add it to the map,
CIGSurfaceLight::CRetrieverLightGrid &rlgDst= _IGRetrieverGridMap[itSrc->first];
// resize Array of surfaces.
rlgDst.Grids.resize(numSurfaces);
// For all surfaces, init them in rlgDst.
for(uint i=0; isecond.Grids[i];
CSurfaceLightGrid &surfDst= rlgDst.Grids[i];
// Init Cells with a default CellCorner
CSurfaceLightGrid::CCellCorner defaultCellCorner;
defaultCellCorner.SunContribution= 0;
defaultCellCorner.Light[0]= 0xFF;
defaultCellCorner.LocalAmbientId= 0xFF;
// Init the grid.
surfDst.Origin= surfSrc.Origin;
surfDst.Width= surfSrc.Width;
surfDst.Height= surfSrc.Height;
surfDst.Cells.resize(surfSrc.Cells.size());
surfDst.Cells.fill(defaultCellCorner);
// The grid must be valid an not empty
nlassert( surfDst.Cells.size() == surfDst.Width*surfDst.Height );
nlassert( surfDst.Width>= 2 );
nlassert( surfDst.Height>= 2 );
_TotalCellNumber+= surfDst.Cells.size();
}
}
// Next localRetriever info.
itSrc++;
}
}
// Reset cell iteration.
_IsEndCell= true;
// Retrieve info from igIn.
//-----------
igIn.retrieve (outGlobalPos, _Instances, outClusters, outPortals, pointLightList);
// set All Instances StaticLightEnabled= true, and Build _InstanceInfos.
//-----------
// Map of shape
std::map shapeMap;
_InstanceInfos.resize(_Instances.size());
for(i=0; i<(sint)_Instances.size();i++)
{
// Avoid StaticLight precomputing??
if(_Instances[i].AvoidStaticLightPreCompute)
{
_Instances[i].StaticLightEnabled= false;
// Next instance.
continue;
}
// Else let's do it.
_Instances[i].StaticLightEnabled= true;
// Get the shape centerPos;
//------------
CVector shapeCenterPos;
CVector overSamples[MaxOverSamples];
// Get the instance shape name
string name= _Instances[i].Name;
bool shapeFound= true;
// Try to find the shape in the UseShapeMap.
std::map::const_iterator iteMap= lightDesc.UserShapeMap.find (name);
// If not found in userShape map, try to load it from the temp loaded ShapeBank.
if( iteMap == lightDesc.UserShapeMap.end() )
{
// Add a .shape at the end ?
if (name.find('.') == std::string::npos)
name += ".shape";
// Get the instance shape name
string nameLookup = CPath::lookup (name, false, false);
if (!nameLookup.empty())
name = nameLookup;
// Find the shape in the bank
iteMap= shapeMap.find (name);
if (iteMap==shapeMap.end())
{
// Input file
CIFile inputFile;
if (!name.empty() && inputFile.open (name))
{
// Load it
CShapeStream stream;
stream.serial (inputFile);
// Get the pointer
iteMap=shapeMap.insert (std::map::value_type (name, stream.getShapePointer ())).first;
}
else
{
// Error
nlwarning ("WARNING can't load shape %s\n", name.c_str());
shapeFound= false;
}
}
}
// Last chance to skip it: fully LightMapped ??
//-----------
if(shapeFound)
{
CMeshBase *mesh= dynamic_cast(iteMap->second);
if(mesh)
{
// If this mesh is not lightable (fully lightMapped)
if(!mesh->isLightable())
{
// Force Avoid StaticLight precomputing
_Instances[i].AvoidStaticLightPreCompute= true;
// Disable static lighting.
_Instances[i].StaticLightEnabled= false;
// Next instance.
continue;
}
}
}
// Compute pos and OverSamples
//-----------
{
// Compute bbox, or default bbox
CAABBox bbox;
if(!shapeFound)
{
bbox.setCenter(CVector::Null);
bbox.setHalfSize(CVector::Null);
}
else
{
iteMap->second->getAABBox(bbox);
}
// get pos
shapeCenterPos= bbox.getCenter();
// Compute overSamples
float qx= bbox.getHalfSize().x/2;
float qy= bbox.getHalfSize().y/2;
float qz= bbox.getHalfSize().z/2;
// No OverSampling => just copy.
if(lightDesc.OverSampling==0)
overSamples[0]= shapeCenterPos;
else if(lightDesc.OverSampling==2)
{
// Prefer Z Axis.
overSamples[0]= shapeCenterPos + CVector(0, 0, qz);
overSamples[1]= shapeCenterPos - CVector(0, 0, qz);
}
else if(lightDesc.OverSampling==4)
{
// Apply an overSampling such that we see 4 points if we look on each side of the bbox.
overSamples[0]= shapeCenterPos + CVector(-qx, -qy, -qz);
overSamples[1]= shapeCenterPos + CVector(+qx, -qy, +qz);
overSamples[2]= shapeCenterPos + CVector(-qx, +qy, +qz);
overSamples[3]= shapeCenterPos + CVector(+qx, +qy, -qz);
}
else if(lightDesc.OverSampling==8 || lightDesc.OverSampling==16)
{
// 8x is the best overSampling shceme for bbox
overSamples[0]= shapeCenterPos + CVector(-qx, -qy, -qz);
overSamples[1]= shapeCenterPos + CVector(+qx, -qy, -qz);
overSamples[2]= shapeCenterPos + CVector(-qx, +qy, -qz);
overSamples[3]= shapeCenterPos + CVector(+qx, +qy, -qz);
overSamples[4]= shapeCenterPos + CVector(-qx, -qy, +qz);
overSamples[5]= shapeCenterPos + CVector(+qx, -qy, +qz);
overSamples[6]= shapeCenterPos + CVector(-qx, +qy, +qz);
overSamples[7]= shapeCenterPos + CVector(+qx, +qy, +qz);
// 16x => use this setup, and decal from 1/8
if(lightDesc.OverSampling==16)
{
CVector decal(qx/2, qy/2, qz/2);
for(uint sample=0; sample<8; sample++)
{
// Copy and decal
overSamples[sample+8]= overSamples[sample] + decal;
// neg decal me
overSamples[sample]-= decal;
}
}
}
}
// Compute pos of the instance
//------------
CMatrix matInst;
matInst.setPos(_Instances[i].Pos);
matInst.setRot(_Instances[i].Rot);
matInst.scale(_Instances[i].Scale);
_InstanceInfos[i].CenterPos= matInst * shapeCenterPos;
// Apply matInst to samples.
uint nSamples= max(1U, lightDesc.OverSampling);
for(uint sample=0; sample::iterator iteMap;
iteMap= shapeMap.begin();
while(iteMap!= shapeMap.end())
{
// delte shape
delete iteMap->second;
// delete entry in map
shapeMap.erase(iteMap);
// next
iteMap= shapeMap.begin();
}
// Build all obstacles plane.
//-----------
for(i=0; i<(sint)obstacles.size();i++)
{
CInstanceLighter::CTriangle& triangle=obstacles[i];
// Calc the plane
triangle.Plane.make (triangle.Triangle.V0, triangle.Triangle.V1, triangle.Triangle.V2);
}
// Lighting
//========
// Light With Sun: build the grid, and do it on all _Instances, using _InstanceInfos
// Compute also Lighting on surface.
computeSunContribution(lightDesc, obstacles, landscape);
// Light With PointLights
// build the cubeGrids
compilePointLightRT(lightDesc.GridSize, lightDesc.GridCellSize, obstacles, lightDesc.Shadow);
// kill pointLightList, because will use mine.
pointLightList.clear();
// Light for all _Instances, using _InstanceInfos
// Compute also Lighting on surface.
processIGPointLightRT(pointLightList);
// If _IGSurfaceLightBuild, then dilate lighting
if(_IGSurfaceLightBuild)
{
dilateLightingOnSurfaceCells();
}
// Build result.
//========
if(_IGSurfaceLightBuild)
{
// build with IGSurfaceLight lighting
igOut.build(outGlobalPos, _Instances, outClusters, outPortals, pointLightList,
&_IGRetrieverGridMap, _IGSurfaceLightBuild->CellSize);
}
else
{
// build without IGSurfaceLight lighting
igOut.build(outGlobalPos, _Instances, outClusters, outPortals, pointLightList);
}
}
// ***************************************************************************
static void NEL3DCalcBase (CVector &direction, CMatrix& matrix)
{
direction.normalize();
CVector I=(fabs(direction*CVector(1.f,0,0))>0.99)?CVector(0.f,1.f,0.f):CVector(1.f,0.f,0.f);
CVector K=-direction;
CVector J=K^I;
J.normalize();
I=J^K;
I.normalize();
matrix.identity();
matrix.setRot(I,J,K, true);
}
// ***************************************************************************
void CInstanceLighter::computeSunContribution(const CLightDesc &lightDesc, std::vector& obstacles, CLandscape *landscape)
{
sint i;
// Use precoputed landscape SunContribution
CVisualCollisionManager *VCM= NULL;
CVisualCollisionEntity *VCE= NULL;
if(landscape)
{
// create a CVisualCollisionManager and a CVisualCollisionEntity
VCM= new CVisualCollisionManager;
VCM->setLandscape(landscape);
VCE= VCM->createEntity();
}
std::vector dummyPointLightFromLandscape;
dummyPointLightFromLandscape.reserve(1024);
// If DisableSunContribution, easy,
if(lightDesc.DisableSunContribution)
{
// Light all instances.
//==========
for(i=0; i<(sint)_Instances.size(); i++)
{
// If staticLight not enabled, skip.
if( !_Instances[i].StaticLightEnabled )
continue;
// fill SunContribution to 0
_Instances[i].SunContribution= 0;
}
// Light SurfaceGrid Cells.
//==========
if(_IGSurfaceLightBuild)
{
// Begin cell iteration
beginCell();
// For all surface cell corners
while( !isEndCell() )
{
// get the current cell and cellInfo iterated.
CIGSurfaceLightBuild::CCellCorner &cellInfo= getCurrentCellInfo();
CSurfaceLightGrid::CCellCorner &cell= getCurrentCell();
// if the cell corner lies in the polygon surface.
if(cellInfo.InSurface)
{
// fill SunContribution to 0
cell.SunContribution= 0;
// copy it to cellInfo
cellInfo.SunContribution= cell.SunContribution;
}
// next cell
nextCell();
}
}
}
// If no Raytrace Shadow, easy,
else if(!lightDesc.Shadow)
{
// Light all instances.
//==========
for(i=0; i<(sint)_Instances.size(); i++)
{
progress ("Compute SunContribution on Instances", i / float(_Instances.size()) );
// If staticLight not enabled, skip.
if( !_Instances[i].StaticLightEnabled )
continue;
// by default, fill SunContribution to 255
_Instances[i].SunContribution= 255;
// Try to get landscape SunContribution (better)
if(landscape)
{
CVector pos= _InstanceInfos[i].CenterPos;
uint8 landSunContribution;
dummyPointLightFromLandscape.clear();
// If find faces under me
NLMISC::CRGBA dummyAmbient;
if(VCE->getStaticLightSetup(NLMISC::CRGBA::Black, pos, dummyPointLightFromLandscape, landSunContribution, dummyAmbient) )
{
_Instances[i].SunContribution= landSunContribution;
}
}
}
// Light SurfaceGrid Cells.
//==========
if(_IGSurfaceLightBuild)
{
// Begin cell iteration
beginCell();
// For all surface cell corners
while( !isEndCell() )
{
progressCell("Compute SunContribution on Surfaces");
// get the current cell and cellInfo iterated.
CIGSurfaceLightBuild::CCellCorner &cellInfo= getCurrentCellInfo();
CSurfaceLightGrid::CCellCorner &cell= getCurrentCell();
// if the cell corner lies in the polygon surface.
if(cellInfo.InSurface)
{
// Just init SunContribution to 255, since no shadowing.
cell.SunContribution= 255;
// copy it to cellInfo
cellInfo.SunContribution= cell.SunContribution;
}
// next cell
nextCell();
}
}
}
else
{
// Compute rayBasis
CVector rayDir= lightDesc.LightDirection;
CMatrix rayBasis;
rayDir.normalize();
NEL3DCalcBase(rayDir, rayBasis);
CMatrix invRayBasis;
invRayBasis= rayBasis.inverted();
// Build QuadGrid of obstacles.
//=========
// setup quadGrid
CQuadGrid quadGrid;
quadGrid.changeBase (invRayBasis);
quadGrid.create(lightDesc.GridSize, lightDesc.GridCellSize);
// Insert all obstacles in quadGrid
for(i=0; i<(sint)obstacles.size(); i++)
{
CAABBox triBBox;
// Compute the bbox in rayBasis.
triBBox.setCenter(invRayBasis * obstacles[i].Triangle.V0);
triBBox.extend(invRayBasis * obstacles[i].Triangle.V1);
triBBox.extend(invRayBasis * obstacles[i].Triangle.V2);
// And set the coord in our world, because will be multiplied with invRayBasis in insert()
quadGrid.insert(rayBasis * triBBox.getMin(), rayBasis * triBBox.getMax(), &obstacles[i]);
}
// For all instances, light them.
//=========
for(i=0; i<(sint)_Instances.size(); i++)
{
progress ("Compute SunContribution on Instances", i / float(_Instances.size()) );
// If staticLight not enabled, skip.
if( !_Instances[i].StaticLightEnabled )
continue;
// try to use landscape SunContribution.
bool landUsed= false;
if(landscape)
{
CVector pos= _InstanceInfos[i].CenterPos;
uint8 landSunContribution;
dummyPointLightFromLandscape.clear();
// If find faces under me
NLMISC::CRGBA dummyAmbient;
if(VCE->getStaticLightSetup(NLMISC::CRGBA::Black, pos, dummyPointLightFromLandscape, landSunContribution, dummyAmbient) )
{
_Instances[i].SunContribution= landSunContribution;
landUsed= true;
}
}
// If failed to use landscape SunContribution, rayTrace
if(!landUsed)
{
// number of samples (1 if no overSampling)
uint nSamples= max(1U, lightDesc.OverSampling);
// Default is full lighted.
uint sunAccum= 255*nSamples;
// For all samples
for(uint sample=0; sample::CIterator it=quadGrid.begin();
while (it!=quadGrid.end())
{
const CTriangle *tri= *it;
// If same instanceId, skip
if(tri->InstanceId != i)
{
CVector hit;
// If triangle occlude the ray, no sun Contribution
if(tri->Triangle.intersect(lightPos, pos, hit, tri->Plane))
{
// The sample is not touched by sun. sub his contribution
sunAccum-= 255;
// End
break;
}
}
it++;
}
}
// Average samples
_Instances[i].SunContribution= sunAccum / nSamples;
}
}
// Light SurfaceGrid Cells.
//==========
if(_IGSurfaceLightBuild)
{
// No instance currenlty computed, since we compute surface cells.
_CurrentInstanceComputed= -1;
// Begin cell iteration
beginCell();
// For all surface cell corners
while( !isEndCell() )
{
progressCell("Compute SunContribution on Surfaces");
// get the current cell and cellInfo iterated.
CIGSurfaceLightBuild::CCellCorner &cellInfo= getCurrentCellInfo();
CSurfaceLightGrid::CCellCorner &cell= getCurrentCell();
// if the cell corner lies in the polygon surface.
if(cellInfo.InSurface)
{
// number of samples (at least 1 if no overSampling)
uint nSamples= cellInfo.NumOverSamples;
nlassert(nSamples>=1);
// Default is full lighted.
uint sunAccum= 255*nSamples;
// For all samples
for(uint sample=0; sample::CIterator it=quadGrid.begin();
while (it!=quadGrid.end())
{
const CTriangle *tri= *it;
CVector hit;
// If triangle occlude the ray, no sun Contribution
if(tri->Triangle.intersect(lightPos, pos, hit, tri->Plane))
{
// The cell sample is not touched by sun. sub his contribution
sunAccum-= 255;
// End
break;
}
it++;
}
}
// Average SunContribution
cell.SunContribution= sunAccum / nSamples;
// copy it to cellInfo
cellInfo.SunContribution= cell.SunContribution;
}
// next cell
nextCell();
}
}
}
// Clean VCM and VCE
if(landscape)
{
// delete CVisualCollisionManager and CVisualCollisionEntity
VCM->deleteEntity(VCE);
delete VCM;
}
}
// ***************************************************************************
// ***************************************************************************
// PointLights part
// ***************************************************************************
// ***************************************************************************
// ***************************************************************************
CInstanceLighter::CPointLightRT::CPointLightRT()
{
RefCount= 0;
}
// ***************************************************************************
bool CInstanceLighter::CPointLightRT::testRaytrace(const CVector &v, sint instanceComputed)
{
CVector dummy;
if(!BSphere.include(v))
return false;
// If Ambient light, just skip
if(PointLight.getType()== CPointLight::AmbientLight)
return false;
// If SpotLight verify in angle radius.
if(PointLight.getType()== CPointLight::SpotLight)
{
float att= PointLight.computeLinearAttenuation(v);
if (att==0)
return false;
}
// Select in the cubeGrid
FaceCubeGrid.select(v);
// For all faces selected
while(!FaceCubeGrid.isEndSel())
{
const CTriangle *tri= FaceCubeGrid.getSel();
// If the triangle is not a triangle of the instance currenlty lighted
if( instanceComputed<0 || tri->InstanceId != instanceComputed )
{
// If intersect, the point is occluded.
if( tri->Triangle.intersect(BSphere.Center, v, dummy, tri->getPlane()) )
return false;
}
// next
FaceCubeGrid.nextSel();
}
// Ok the point is visilbe from the light
return true;
}
// ***************************************************************************
void CInstanceLighter::addStaticPointLight(const CPointLightNamed &pln, const char *igName)
{
// NB: adding light more than 255 is allowed here, since the important thing is to not overflow really useful lights
// build the plRT.
CPointLightRT plRT;
plRT.PointLight= pln;
// compute plRT.OODeltaAttenuation
plRT.OODeltaAttenuation= pln.getAttenuationEnd() - pln.getAttenuationBegin();
if(plRT.OODeltaAttenuation <=0 )
plRT.OODeltaAttenuation= 1e10f;
else
plRT.OODeltaAttenuation= 1.0f / plRT.OODeltaAttenuation;
// compute plRT.BSphere
plRT.BSphere.Center= pln.getPosition();
plRT.BSphere.Radius= pln.getAttenuationEnd();
// NB: FaceCubeGrid will be computed during light()
// add the plRT
_StaticPointLights.push_back(plRT);
}
// ***************************************************************************
void CInstanceLighter::compilePointLightRT(uint gridSize, float gridCellSize, std::vector& obstacles, bool doShadow)
{
uint i;
// Fill the quadGrid of Lights.
// ===========
_StaticPointLightQuadGrid.create(gridSize, gridCellSize);
for(i=0; i<_StaticPointLights.size();i++)
{
CPointLightRT &plRT= _StaticPointLights[i];
// Compute the bbox of the light
CAABBox bbox;
bbox.setCenter(plRT.BSphere.Center);
float hl= plRT.BSphere.Radius;
bbox.setHalfSize(CVector(hl,hl,hl));
// Insert the pointLight in the quadGrid.
_StaticPointLightQuadGrid.insert(bbox.getMin(), bbox.getMax(), &plRT);
}
// Append triangles to cubeGrid ??
if(doShadow)
{
// For all obstacles, Fill a quadGrid.
// ===========
CQuadGrid obstacleGrid;
obstacleGrid.create(gridSize, gridCellSize);
uint size= obstacles.size();
for(i=0; i::CIterator itObstacle;
itObstacle= obstacleGrid.begin();
while( itObstacle!=obstacleGrid.end() )
{
CTriangle &tri= *(*itObstacle);
/* Don't Test BackFace culling Here (unlike in CZoneLighter !!).
For objects:
AutoOccluding problem is avoided with _CurrentInstanceComputed scheme.
Also, With pointLights, there is no multiSampling (since no factor stored)
Hence we are sure that no Object samples will lies under floor, and that the center of the
object is far away.
For IGSurface lighting:
notice that we already add 20cm in height because of "stairs problem" so
floor/surface auto_shadowing is not a problem here...
*/
// Insert the triangle in the CubeGrid
plRT.FaceCubeGrid.insert( tri.Triangle, &tri);
itObstacle++;
}
}
// Compile the CubeGrid.
plRT.FaceCubeGrid.compile();
// And Reset RefCount.
plRT.RefCount= 0;
}
}
// else, just build empty grid
else
{
for(i=0; i<_StaticPointLights.size();i++)
{
// progress
progress ("Compute Influences of PointLights 1/2", i / (float)_StaticPointLights.size());
CPointLightRT &plRT= _StaticPointLights[i];
// Create a dummy empty cubeGrid => no rayTrace :)
plRT.FaceCubeGrid.create(plRT.PointLight.getPosition(), 4);
// Compile the CubeGrid.
plRT.FaceCubeGrid.compile();
// And Reset RefCount.
plRT.RefCount= 0;
}
}
}
// ***************************************************************************
bool CInstanceLighter::CPredPointLightToPoint::operator() (CPointLightRT *pla, CPointLightRT *plb) const
{
float ra= (pla->BSphere.Center - Point).norm();
float rb= (plb->BSphere.Center - Point).norm();
float infA= (pla->PointLight.getAttenuationEnd() - ra) * pla->OODeltaAttenuation;
float infB= (plb->PointLight.getAttenuationEnd() - rb) * plb->OODeltaAttenuation;
// It is important to clamp, else strange results...
clamp(infA, 0.f, 1.f);
clamp(infB, 0.f, 1.f);
// return which light impact the most.
// If same impact
if(infA==infB)
// return nearest
return ra < rb;
else
// return better impact
return infA > infB;
}
// ***************************************************************************
void CInstanceLighter::processIGPointLightRT(std::vector &listPointLight)
{
uint i;
vector lightInfs;
lightInfs.reserve(1024);
// clear result list
listPointLight.clear();
// Compute each Instance
//===========
for(i=0; i<_InstanceInfos.size(); i++)
{
// If staticLight not enabled, skip.
if( !_Instances[i].StaticLightEnabled )
continue;
CInstanceInfo &inst= _InstanceInfos[i];
// Avoid autoShadowing
_CurrentInstanceComputed= i;
// progress
progress ("Compute Influences of PointLights 2/2", i / (float)_InstanceInfos.size());
// get the point of the instance.
CVector pos= inst.CenterPos;
// Default: takes no LocalAmbientLight;
inst.LocalAmbientLight= NULL;
float furtherAmbLight= 0;
// Compute Which light influences him.
//---------
lightInfs.clear();
// Search possible lights around the position.
_StaticPointLightQuadGrid.select(pos, pos);
// For all of them, get the ones which touch this point.
CQuadGrid::CIterator it= _StaticPointLightQuadGrid.begin();
while(it != _StaticPointLightQuadGrid.end())
{
CPointLightRT *pl= *it;
// Test if really in the radius of the light, no occlusion, not an ambient, and in Spot Angle setup
if( pl->testRaytrace(pos, _CurrentInstanceComputed) )
{
// Ok, add the light to the lights which influence the instance
lightInfs.push_back(pl);
}
// Ambient Light ??
if( pl->PointLight.getType() == CPointLight::AmbientLight )
{
// If the instance is in radius of the ambiant light.
float dRadius= pl->BSphere.Radius - (pl->BSphere.Center - pos).norm();
if(dRadius>0)
{
// Take the best ambient light: the one which is further from the circumference
if(dRadius > furtherAmbLight)
{
furtherAmbLight= dRadius;
inst.LocalAmbientLight= pl;
}
}
}
// next
it++;
}
// If ambientLight chosen, inc Ref count of it
if(inst.LocalAmbientLight)
inst.LocalAmbientLight->RefCount++;
// Choose the Best ones.
//---------
CPredPointLightToPoint predPLTP;
predPLTP.Point= pos;
// sort.
sort(lightInfs.begin(), lightInfs.end(), predPLTP);
// truncate.
lightInfs.resize( min((uint)lightInfs.size(), (uint)CInstanceGroup::NumStaticLightPerInstance) );
// For each of them, fill instance
//---------
uint lightInfId;
for(lightInfId=0; lightInfIdRefCount++;
}
// Reset any empty slot to NULL.
for(; lightInfId::CIterator it= _StaticPointLightQuadGrid.begin();
while(it != _StaticPointLightQuadGrid.end())
{
CPointLightRT *pl= *it;
// Test if really in the radius of the light, no occlusion, not an ambient, and in Spot Angle setup
if( pl->testRaytrace(pos, _CurrentInstanceComputed) )
{
// Ok, add the light to the lights which influence the cell
lightInfs.push_back(pl);
}
// Ambient Light ??
if( pl->PointLight.getType() == CPointLight::AmbientLight )
{
// If the instance is in radius of the ambiant light.
float dRadius= pl->BSphere.Radius - (pl->BSphere.Center - pos).norm();
if(dRadius>0)
{
// Take the best ambient light: the one which is further from the circumference
if(dRadius > furtherAmbLight)
{
furtherAmbLight= dRadius;
cellInfo.LocalAmbientLight= pl;
}
}
}
// next
it++;
}
// If ambientLight chosen, inc Ref count of it
if(cellInfo.LocalAmbientLight)
((CPointLightRT*)cellInfo.LocalAmbientLight)->RefCount++;
// Choose the Best ones.
//---------
CPredPointLightToPoint predPLTP;
predPLTP.Point= pos;
// sort.
sort(lightInfs.begin(), lightInfs.end(), predPLTP);
// truncate.
lightInfs.resize( min((uint)lightInfs.size(), (uint)CSurfaceLightGrid::NumLightPerCorner) );
// For each of them, fill cellInfo
//---------
uint lightInfId;
for(lightInfId=0; lightInfIdRefCount++;
}
// Reset any empty slot to NULL.
for(; lightInfId 0)
{
// Valid light ?
if (plId <=0xFF)
{
// Must Copy it into Ig.
listPointLight.push_back(plRT.PointLight);
plRT.DstId= plId++;
// If index >= 255, too many lights (NB: => because 255 is a NULL code).
}
else
{
nlwarning("ERROR: Too many Static Point Lights influence the IG!!");
// Set 0xFF. Special code indicating that the light CAN'T BE USED => any instance using
// it is buggy (won't be lighted by this light).
plRT.DstId= plId++;
}
}
}
// For each instance, compress Point light info
for(i=0; i<_Instances.size(); i++)
{
// If staticLight not enabled, skip.
if( !_Instances[i].StaticLightEnabled )
continue;
CInstanceInfo &instSrc= _InstanceInfos[i];
CInstanceGroup::CInstance &instDst= _Instances[i];
// Do it for PointLights
for(uint lightId= 0; lightIdDstId;
}
}
// Ensure that all FF are at end of the list (possible because of the TooManyStaticLight bug).
// But don't do a full sort, to preserve order due to influence...
nlctassert(CInstanceGroup::NumStaticLightPerInstance==2);
if(instDst.Light[0] == 0xFF) swap(instDst.Light[0], instDst.Light[1]);
// Do it for Ambientlight
if(instSrc.LocalAmbientLight == NULL)
instDst.LocalAmbientId= 0xFF;
else
// NB: may still be 0xFF if 'Too many static light' bug.
instDst.LocalAmbientId= instSrc.LocalAmbientLight->DstId;
}
// For each cell, compress Point light info
if(_IGSurfaceLightBuild)
{
// Begin cell iteration
beginCell();
// For all surface cell corners
while( !isEndCell() )
{
// get the current cell and cellInfo iterated.
CIGSurfaceLightBuild::CCellCorner &cellInfo= getCurrentCellInfo();
CSurfaceLightGrid::CCellCorner &cell= getCurrentCell();
if(cellInfo.InSurface)
{
// Do it for PointLights
for(uint lightId= 0; lightId(cellInfo.LightInfo[lightId])->DstId;
}
}
// Ensure that all FF are at end of the list (possible because of the TooManyStaticLight bug).
// But don't do a full sort, to preserve order due to influence...
nlctassert(CInstanceGroup::NumStaticLightPerInstance==2);
if(cell.Light[0] == 0xFF) swap(cell.Light[0], cell.Light[1]);
// Do it for Ambientlight
if(cellInfo.LocalAmbientLight == NULL)
cell.LocalAmbientId= 0xFF;
else
// NB: may still be 0xFF if 'Too many static light' bug.
cell.LocalAmbientId= ((CPointLightRT*)cellInfo.LocalAmbientLight)->DstId;
}
// next cell
nextCell();
}
}
}
// ***************************************************************************
// ***************************************************************************
// lightIgSimple
// ***************************************************************************
// ***************************************************************************
// ***************************************************************************
void CInstanceLighter::lightIgSimple(CInstanceLighter &instLighter, const CInstanceGroup &igIn, CInstanceGroup &igOut, const CLightDesc &lightDesc, const char *igName)
{
sint i;
// Setup.
//=======
// Init
instLighter.init();
// Add obstacles.
std::vector obstacles;
// only if Shadowing On.
if(lightDesc.Shadow)
{
// Map of shape to load
std::map shapeMap;
// For all instances of igIn.
for(i=0; i<(sint)igIn.getNumInstance();i++)
{
// progress
instLighter.progress("Loading Shapes obstacles", float(i)/igIn.getNumInstance());
// Skip it??
if(igIn.getInstance(i).DontCastShadow)
continue;
// Get the instance shape name
string name= igIn.getShapeName(i);
bool shapeFound= true;
// Try to find the shape in the UseShapeMap.
std::map::const_iterator iteMap= lightDesc.UserShapeMap.find (name);
// If not found in userShape map, try to load it from the temp loaded ShapeBank.
if( iteMap == lightDesc.UserShapeMap.end() )
{
// Add a .shape at the end ?
if (name.find('.') == std::string::npos)
name += ".shape";
// Get the instance shape name
string nameLookup = CPath::lookup (name, false, false);
if (!nameLookup.empty())
name = nameLookup;
// Find the shape in the bank
iteMap= shapeMap.find (name);
if (iteMap==shapeMap.end())
{
// Input file
CIFile inputFile;
if (!name.empty() && inputFile.open (name))
{
// Load it
CShapeStream stream;
stream.serial (inputFile);
// Get the pointer
iteMap=shapeMap.insert (std::map::value_type (name, stream.getShapePointer ())).first;
}
else
{
// Error
nlwarning ("WARNING can't load shape %s\n", name.c_str());
shapeFound= false;
}
}
}
if(shapeFound)
{
CMatrix matInst;
matInst.setPos(igIn.getInstancePos(i));
matInst.setRot(igIn.getInstanceRot(i));
matInst.scale(igIn.getInstanceScale(i));
// Add triangles of this shape
CInstanceLighter::addTriangles(*iteMap->second, matInst, obstacles, i);
}
}
// Clean Up shapes.
//-----------
std::map::iterator iteMap;
iteMap= shapeMap.begin();
while(iteMap!= shapeMap.end())
{
// delte shape
delete iteMap->second;
// delete entry in map
shapeMap.erase(iteMap);
// next
iteMap= shapeMap.begin();
}
}
// Add pointLights of the IG.
for(i=0; i<(sint)igIn.getPointLightList().size();i++)
{
instLighter.addStaticPointLight( igIn.getPointLightList()[i], igName );
}
// Run.
//=======
instLighter.light(igIn, igOut, lightDesc, obstacles);
}
// ***************************************************************************
// ***************************************************************************
// Cell Iteration
// ***************************************************************************
// ***************************************************************************
// ***************************************************************************
void CInstanceLighter::progressCell(const char *message)
{
float cp= getCurrentCellNumber() / float(getTotalCellNumber());
if( cp > _LastCellProgress+0.05f)
{
progress(message, cp);
_LastCellProgress= cp;
}
}
// ***************************************************************************
void CInstanceLighter::beginCell()
{
if(_IGSurfaceLightBuild)
{
_ItRetriever= _IGRetrieverGridMap.begin();
if(_ItRetriever != _IGRetrieverGridMap.end() )
{
_ItRetrieverInfo= _IGSurfaceLightBuild->RetrieverGridMap.find(_ItRetriever->first);
nlassert(_ItRetrieverInfo != _IGSurfaceLightBuild->RetrieverGridMap.end() );
// We are suze here that the retriever is not empty, and that the grid herself is not empty too
_ItSurfId= 0;
_ItCellId= 0;
_ItCurrentCellNumber= 0;
_IsEndCell= false;
_LastCellProgress= 0;
}
else
{
_IsEndCell= true;
}
}
else
{
_IsEndCell= true;
}
}
// ***************************************************************************
void CInstanceLighter::nextCell()
{
nlassert(!isEndCell());
// Next Cell.
_ItCellId++;
_ItCurrentCellNumber++;
// If end of Cells, next surface.
if(_ItCellId >= _ItRetriever->second.Grids[_ItSurfId].Cells.size() )
{
_ItCellId= 0;
_ItSurfId ++;
}
// If end of surface, next retriever.
if(_ItSurfId >= _ItRetriever->second.Grids.size() )
{
_ItSurfId= 0;
_ItRetriever++;
if(_ItRetriever != _IGRetrieverGridMap.end())
{
// Get info.
_ItRetrieverInfo= _IGSurfaceLightBuild->RetrieverGridMap.find(_ItRetriever->first);
nlassert(_ItRetrieverInfo != _IGSurfaceLightBuild->RetrieverGridMap.end() );
}
}
// If end of retreiver, End.
if(_ItRetriever == _IGRetrieverGridMap.end())
{
_IsEndCell= true;
}
}
// ***************************************************************************
bool CInstanceLighter::isEndCell()
{
return _IsEndCell;
}
// ***************************************************************************
CSurfaceLightGrid::CCellCorner &CInstanceLighter::getCurrentCell()
{
nlassert(!isEndCell());
// return ref on Cell.
return _ItRetriever->second.Grids[_ItSurfId].Cells[_ItCellId];
}
// ***************************************************************************
CIGSurfaceLightBuild::CCellCorner &CInstanceLighter::getCurrentCellInfo()
{
nlassert(!isEndCell());
// return ref on CellInfo.
return _ItRetrieverInfo->second.Grids[_ItSurfId].Cells[_ItCellId];
}
// ***************************************************************************
bool CInstanceLighter::isCurrentNeighborCellInSurface(sint xnb, sint ynb)
{
nlassert(!isEndCell());
// get a ref on the current grid.
CSurfaceLightGrid &surfGrid= _ItRetriever->second.Grids[_ItSurfId];
// copute coordinate of the current cellCorner.
sint xCell, yCell;
xCell= _ItCellId%surfGrid.Width;
yCell= _ItCellId/surfGrid.Width;
// compute coordinate of the neighbor cell corner
xCell+= xnb;
yCell+= ynb;
// check if in the surfaceGrid
if(xCell<0 || xCell>=(sint)surfGrid.Width)
return false;
if(yCell<0 || yCell>=(sint)surfGrid.Height)
return false;
// compute the neighbor id
uint nbId= yCell*surfGrid.Width + xCell;
// Now check in the cellInfo if this cell is InSurface.
if( !_ItRetrieverInfo->second.Grids[_ItSurfId].Cells[nbId].InSurface )
return false;
// Ok, the neighbor cell is valid.
return true;
}
// ***************************************************************************
CSurfaceLightGrid::CCellCorner &CInstanceLighter::getCurrentNeighborCell(sint xnb, sint ynb)
{
nlassert(isCurrentNeighborCellInSurface(xnb, ynb));
// get a ref on the current grid.
CSurfaceLightGrid &surfGrid= _ItRetriever->second.Grids[_ItSurfId];
// copute coordinate of the current cellCorner.
sint xCell, yCell;
xCell= _ItCellId%surfGrid.Width;
yCell= _ItCellId/surfGrid.Width;
// compute coordinate of the neighbor cell corner
xCell+= xnb;
yCell+= ynb;
// compute the neighbor id
uint nbId= yCell*surfGrid.Width + xCell;
// then return a ref on it
return surfGrid.Cells[nbId];
}
// ***************************************************************************
CIGSurfaceLightBuild::CCellCorner &CInstanceLighter::getCurrentNeighborCellInfo(sint xnb, sint ynb)
{
nlassert(isCurrentNeighborCellInSurface(xnb, ynb));
// get a ref on the current grid.
CIGSurfaceLightBuild::CSurface &surfGrid= _ItRetrieverInfo->second.Grids[_ItSurfId];
// copute coordinate of the current cellCorner.
sint xCell, yCell;
xCell= _ItCellId%surfGrid.Width;
yCell= _ItCellId/surfGrid.Width;
// compute coordinate of the neighbor cell corner
xCell+= xnb;
yCell+= ynb;
// compute the neighbor id
uint nbId= yCell*surfGrid.Width + xCell;
// then return a ref on it
return surfGrid.Cells[nbId];
}
// ***************************************************************************
void CInstanceLighter::dilateLightingOnSurfaceCells()
{
// Begin cell iteration
beginCell();
// For all surface cell corners
while( !isEndCell() )
{
progressCell("Dilate Surfaces grids");
// get the current cell and cellInfo iterated.
CIGSurfaceLightBuild::CCellCorner &cellInfo= getCurrentCellInfo();
CSurfaceLightGrid::CCellCorner &cell= getCurrentCell();
// if the cell is not in the polygon surface, try to get from his neighbors.
if(!cellInfo.InSurface)
{
// Add Weighted influence of SunContribution, and get one of the PointLightContribution (random).
uint wgtSunContribution= 0;
uint wgtSunCount= 0;
// search if one of 8 neighbors is InSurface.
for(sint ynb= -1; ynb<= 1; ynb++)
{
for(sint xnb= -1; xnb<= 1; xnb++)
{
// center => skip.
if( xnb==0 && ynb==0 )
continue;
// If the neighbor point is not out of the grid, and if in Surface.
if( isCurrentNeighborCellInSurface(xnb, ynb) )
{
// get the neighbor cell
CIGSurfaceLightBuild::CCellCorner &nbCellInfo= getCurrentNeighborCellInfo(xnb, ynb);
CSurfaceLightGrid::CCellCorner &nbCell= getCurrentNeighborCell(xnb, ynb);
// Add SunContribution.
wgtSunContribution+= nbCell.SunContribution;
wgtSunCount++;
// Just Copy PointLight info.
for(uint lightId= 0; lightId0)
{
cell.SunContribution= wgtSunContribution / wgtSunCount;
// For debug mesh only, copy SunContribution into cellInfo
cellInfo.SunContribution= cell.SunContribution;
cellInfo.Dilated= true;
}
}
// next cell
nextCell();
}
}
} // NL3D