Merge branch 'ryzom/ark-features' into main/gingo-test
[ryzomcore.git] / nel / src / 3d / lighting_manager.cpp
blobdcf6eff80d43d0c8b37c19184face31c869ef201
1 // NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
2 // Copyright (C) 2010 Winch Gate Property Limited
3 //
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU Affero General Public License as
6 // published by the Free Software Foundation, either version 3 of the
7 // License, or (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU Affero General Public License for more details.
14 // You should have received a copy of the GNU Affero General Public License
15 // along with this program. If not, see <http://www.gnu.org/licenses/>.
17 #include "std3d.h"
19 #include "nel/3d/lighting_manager.h"
20 #include "nel/3d/point_light.h"
21 #include "nel/3d/transform.h"
22 #include "nel/misc/fast_floor.h"
23 #include "nel/3d/logic_info.h"
24 #include "nel/misc/aabbox.h"
25 #include "nel/misc/algo.h"
28 using namespace NLMISC;
29 using namespace std;
31 #ifdef DEBUG_NEW
32 #define new DEBUG_NEW
33 #endif
35 namespace NL3D {
38 // ***************************************************************************
39 /* LightQuadGrid setup. This is the same setup for StaticLightedModelQuadGrid setup.
40 NB: with this setup, a light will lies into 4*4=16 squares of a quadGrid at max.
42 #define NL3D_LIGHT_QUAD_GRID_SIZE 128
43 #define NL3D_LIGHT_QUAD_GRID_ELTSIZE 10.f
44 #define NL3D_LIGHT_QUAD_GRID_RADIUS_LIMIT 20.f
45 // Factor for a level to the next: size/=factor, eltSize*=factor, and radiusLimit*=factor.
46 #define NL3D_LIGHT_QUAD_GRID_FACTOR 4
49 // this is a big radius for light that don't have attenuation
50 #define NL3D_DEFAULT_NOATT_LIGHT_RADIUS 1000.f
51 // this is used when the model is out of attBegin/attEnd, to modulate the influence
52 #define NL3D_DEFAULT_OUT_OF_ATT_LIGHT_INF_FACTOR 0.1f
53 // Defualt LightTransitionThreshold
54 #define NL3D_DEFAULT_LIGHT_TRANSITION_THRESHOLD 0.1f
57 // ***************************************************************************
58 CLightingManager::CLightingManager(bool bSmallScene)
60 // Init the lightQuadGrids and StaticLightedModelQuadGrid
61 // finer level.
62 uint qgSize= NL3D_LIGHT_QUAD_GRID_SIZE;
63 float eltSize= NL3D_LIGHT_QUAD_GRID_ELTSIZE;
64 float radiusLimit= NL3D_LIGHT_QUAD_GRID_RADIUS_LIMIT;
66 if (bSmallScene)
68 qgSize = 4;
71 // for all levels
72 for(uint i=0;i<NL3D_QUADGRID_LIGHT_NUM_LEVEL;i++)
74 // init _LightQuadGrid and _StaticLightedModelQuadGrid
75 _LightQuadGrid[i].create(qgSize, eltSize);
76 _StaticLightedModelQuadGrid[i].create(qgSize, eltSize);
77 _LightQuadGridRadiusLimit[i]= radiusLimit;
79 // coarser quadGrid level
80 qgSize/= NL3D_LIGHT_QUAD_GRID_FACTOR;
81 qgSize= max(qgSize, 1U);
82 eltSize*= NL3D_LIGHT_QUAD_GRID_FACTOR;
83 radiusLimit*= NL3D_LIGHT_QUAD_GRID_FACTOR;
87 // default paramters
88 _NoAttLightRadius= NL3D_DEFAULT_NOATT_LIGHT_RADIUS;
89 _OutOfAttLightInfFactor= NL3D_DEFAULT_OUT_OF_ATT_LIGHT_INF_FACTOR;
90 _LightTransitionThreshold= NL3D_DEFAULT_LIGHT_TRANSITION_THRESHOLD;
93 // Default number of pointLight on an object.
94 setMaxLightContribution(3);
98 // ***************************************************************************
99 void CLightingManager::setMaxLightContribution(uint nlights)
101 _MaxLightContribution= min(nlights, (uint)NL3D_MAX_LIGHT_CONTRIBUTION);
105 // ***************************************************************************
106 void CLightingManager::setNoAttLightRadius(float noAttLightRadius)
108 nlassert(noAttLightRadius>0);
109 _NoAttLightRadius= noAttLightRadius;
113 // ***************************************************************************
114 void CLightingManager::setOutOfAttLightInfFactor(float outOfAttLightInfFactor)
116 outOfAttLightInfFactor= max(0.f, outOfAttLightInfFactor);
117 _OutOfAttLightInfFactor= outOfAttLightInfFactor;
121 // ***************************************************************************
122 void CLightingManager::setLightTransitionThreshold(float lightTransitionThreshold)
124 clamp(lightTransitionThreshold, 0.f, 1.f);
125 _LightTransitionThreshold= lightTransitionThreshold;
130 // ***************************************************************************
131 void CLightingManager::clearDynamicLights()
133 // for all levels
134 for(uint i=0;i<NL3D_QUADGRID_LIGHT_NUM_LEVEL;i++)
136 // clear all the lights in the quadGrid.
137 _LightQuadGrid[i].clear();
140 // clear the list.
141 _DynamicLightList.clear();
144 // ***************************************************************************
145 void CLightingManager::addDynamicLight(CPointLight *light)
148 // Insert the light in the quadGrid.
149 //----------
150 CPointLightInfo plInfo;
151 plInfo.Light= light;
153 // build the bounding sphere for this light, with the AttenuationEnd of the light
154 float radius=light->getAttenuationEnd();
155 // if attenuation is disabled (ie getAttenuationEnd() return 0), then have a dummy radius
156 if(radius==0)
157 radius= _NoAttLightRadius;
158 plInfo.Sphere.Center= light->getPosition();
159 plInfo.Sphere.Radius= radius;
161 // build a bbox, so it includes the sphere
162 CVector bbmin= light->getPosition();
163 bbmin-= CVector(radius, radius, radius);
164 CVector bbmax= light->getPosition();
165 bbmax+= CVector(radius, radius, radius);
167 // choose the correct quadgrid according to the radius of the light.
168 uint qgId;
169 for(qgId= 0; qgId<NL3D_QUADGRID_LIGHT_NUM_LEVEL-1; qgId++)
171 // if radius is inferior to the requested radius for this quadGrid, ok!
172 if(radius<_LightQuadGridRadiusLimit[qgId])
173 break;
175 // insert this light in the correct quadgrid.
176 _LightQuadGrid[qgId].insert(bbmin, bbmax, plInfo);
179 // touch the objects around the lights.
180 //----------
182 // Select the static lightedModels to update around this light.
183 // Use the same level of _StaticLightedModelQuadGrid than me to not select too many squares in the quadGrid
184 _StaticLightedModelQuadGrid[qgId].select(bbmin, bbmax);
185 // For all those models.
186 CQuadGrid<CTransform*>::CIterator itModel= _StaticLightedModelQuadGrid[qgId].begin();
187 while(itModel != _StaticLightedModelQuadGrid[qgId].end() )
189 CTransform *model= *itModel;
190 const CVector &modelPos= model->getWorldMatrix().getPos();
192 // test first if this model is in the area of the light.
193 if( plInfo.Sphere.include(modelPos) )
195 // yes, then this model must recompute his lighting, because a dynamic light touch him.
196 model->resetLighting();
199 // next.
200 itModel++;
204 // insert the light in the list.
205 //----------
206 _DynamicLightList.push_back(light);
210 // ***************************************************************************
211 CLightingManager::CQGItLightedModel CLightingManager::eraseStaticLightedModel(CQGItLightedModel ite)
213 // Erase the iterator for all levels
214 for(uint i=0;i<NL3D_QUADGRID_LIGHT_NUM_LEVEL;i++)
216 // NB: it is possible here that the iterator is NULL (ie end()).
217 _StaticLightedModelQuadGrid[i].erase(ite.QgItes[i]);
220 // return end(), ie NULL iterators
221 return CQGItLightedModel();
224 // ***************************************************************************
225 CLightingManager::CQGItLightedModel CLightingManager::insertStaticLightedModel(CTransform *model)
227 CQGItLightedModel ite;
228 const CVector &worldPos= model->getWorldMatrix().getPos();
230 // Insert the models in all levels, because addDynamicLight() may choose the best suited level to select
231 for(uint i=0;i<NL3D_QUADGRID_LIGHT_NUM_LEVEL;i++)
233 ite.QgItes[i]= _StaticLightedModelQuadGrid[i].insert(worldPos, worldPos, model);
236 // return the iterator
237 return ite;
241 // ***************************************************************************
242 struct CSortLight
244 CPointLight *PointLight;
245 float Influence;
249 // ***************************************************************************
250 void CLightingManager::computeModelLightContributions(NLMISC::CRGBA sunAmbient, CTransform *model, CLightContribution &lightContrib,
251 ILogicInfo *logicInfo)
253 sint i;
255 // This is the list of light which touch this model.
256 static std::vector<CPointLightInfluence> lightList;
257 // static, for malloc perf.
258 lightList.clear();
260 // the position of the model.
261 CVector modelPos;
262 float modelRadius;
264 // depends on model
265 model->getLightHotSpotInWorld(modelPos, modelRadius);
267 // First pass, fill the list of light which touch this model.
268 //=========
269 // get the dynamic lights around the model
270 getDynamicPointLightList(modelPos, lightList);
272 // if not already precomputed, append staticLights to this list.
273 if( !lightContrib.FrozenStaticLightSetup )
275 // If no logicInfo provided
276 if(!logicInfo)
278 // Default: suppose full SunLight and no PointLights.
279 lightContrib.SunContribution= 255;
280 // Take full SunAmbient.
281 lightContrib.LocalAmbient= sunAmbient;
282 // do not append any pointLight to the setup
284 else
286 // NB: SunContribution is computed by logicInfo
287 logicInfo->getStaticLightSetup(sunAmbient, lightList, lightContrib.SunContribution, lightContrib.LocalAmbient);
291 // Second pass, in the lightList, choose the best suited light to lit this model
292 //=========
294 // for each light, modulate the factor of influence
295 for(i=0; i<(sint)lightList.size();i++)
297 CPointLight *pl= lightList[i].PointLight;
299 // get the distance from the light to the model
300 float dist= (pl->getPosition() - modelPos).norm();
301 float distMinusRadius= dist - modelRadius;
303 // modulate the factor by the distance and the attenuation distance.
304 float inf;
305 float attBegin= pl->getAttenuationBegin();
306 float attEnd= pl->getAttenuationEnd();
307 // if no attenuation
308 if( attEnd==0 )
310 // influence is awlays 1.
311 inf= 1;
313 // If SpotLight, must modulate with SpotAttenuation.
314 if(pl->getType() == CPointLight::SpotLight)
315 inf*= pl->computeLinearAttenuation(modelPos, dist, modelRadius);
317 else
319 // if correct attenuation radius
320 if(distMinusRadius<attBegin)
322 // NB: we are sure that attBegin>0, because dist>=0.
323 // if < attBegin, inf should be ==1, but must select the nearest lights; for better
324 // understanding of the scene
325 inf= 1 + _OutOfAttLightInfFactor * (attBegin - distMinusRadius); // inf E [1, +oo[
326 // NB: this formula favour big lights (ie light with big attBegin).
328 // If SpotLight, must modulate with SpotAttenuation.
329 if(pl->getType() == CPointLight::SpotLight)
330 inf*= pl->computeLinearAttenuation(modelPos, dist, modelRadius);
332 else if(distMinusRadius<attEnd)
334 // we are sure attEnd-attBegin>0 because of the test
335 // compute influence of the light: attenuation and SpotAttenuation
336 inf= pl->computeLinearAttenuation(modelPos, dist, modelRadius);
338 else
340 // if >= attEnd, inf should be ==0, but must select the nearest lights; for better
341 // understanding of the scene
342 inf= _OutOfAttLightInfFactor * (attEnd - distMinusRadius); // inf E ]-oo, 0]
346 // modulate the influence with this factor
347 lightList[i].BkupInfluence= lightList[i].Influence;
348 lightList[i].Influence*= inf;
350 // Bkup distance to model.
351 lightList[i].DistanceToModel= dist;
354 // sort the light by influence
355 sort(lightList.begin(), lightList.end());
357 // prepare Light Merging.
358 static std::vector<float> lightToMergeWeight;
359 lightToMergeWeight.clear();
360 lightToMergeWeight.resize(lightList.size(), 1);
363 // and choose only max light.
364 uint startId= 0;
365 uint ligthSrcId= 0;
366 // skip already setuped light (statically)
367 if(lightContrib.FrozenStaticLightSetup)
368 startId= lightContrib.NumFrozenStaticLight;
370 // If there is still place for unFrozen static lights or dynamic lights.
371 if(startId < _MaxLightContribution)
373 // setup the transition.
374 float deltaMinInfluence= _LightTransitionThreshold;
375 float minInfluence= 0;
376 sint doMerge= 0;
377 sint firstLightToMergeFull= _MaxLightContribution-startId;
378 // If there is more light than we can accept in not merged mode, Must merge
379 if((sint)lightList.size() > firstLightToMergeFull)
381 doMerge= 1;
382 // the minInfluence is the influence of the first light not taken.
383 minInfluence= lightList[firstLightToMergeFull].Influence;
384 // but must still be >=0.
385 minInfluence= max(minInfluence, 0.f);
387 // Any light under this minInfluence+deltaMinInfluence will be smoothly darken.
388 float minInfluenceStart = minInfluence + deltaMinInfluence;
389 float OOdeltaMinInfluence= 1.0f / deltaMinInfluence;
390 /* NB: this is not an error if we have only 3 light for example (assuming _MaxLightContribution=3),
391 and still have minInfluenceStart>0.
392 It's to have a continuity in the case of for example we have 3 lights in lightLists at frame T0,
393 resulting in "doMerge=0" and at frame T1 we have 4 lights, the farthest having an influence of 0
394 or nearly 0.
397 // fill maxLight at max.
398 for(i=startId;i<(sint)_MaxLightContribution; i++)
400 // if not so many pointLights found, end!!
401 if(ligthSrcId>=lightList.size())
402 break;
403 else
405 CPointLight *pl= lightList[ligthSrcId].PointLight;
406 float inf= lightList[ligthSrcId].Influence;
407 float bkupInf= lightList[ligthSrcId].BkupInfluence;
408 float distToModel= lightList[ligthSrcId].DistanceToModel;
409 // else fill it.
410 lightContrib.PointLight[i]= pl;
412 // Compute the Final factor of influence of the light.
413 if(inf >= minInfluenceStart)
415 // For Static LightSetup BiLinear to work correctly, modulate with BkupInfluence
416 // don't worry about the precision of floor, because of *255.
417 lightContrib.Factor[i]= (uint8)NLMISC::OptFastFloor(bkupInf*255);
418 // Indicate that this light don't need to be merged at all!
419 lightToMergeWeight[ligthSrcId]= 0;
421 else
423 float f= (inf-minInfluence) * OOdeltaMinInfluence;
424 // For Static LightSetup BiLinear to work correctly, modulate with BkupInfluence
425 // don't worry about the precision of floor, because of *255.
426 sint fi= NLMISC::OptFastFloor( bkupInf*f*255 );
427 clamp(fi, 0, 255);
428 lightContrib.Factor[i]= fi;
429 // The rest of the light contribution is to be merged.
430 lightToMergeWeight[ligthSrcId]= 1-f;
433 // Compute the Final Att factor for models using Global Attenuation. NB: modulate with Factor
434 // don't worry about the precision of floor, because of *255.
435 // NB: compute att on the center of the model => modelRadius==0
436 sint attFactor= NLMISC::OptFastFloor( lightContrib.Factor[i] * pl->computeLinearAttenuation(modelPos, distToModel) );
437 lightContrib.AttFactor[i]= (uint8)attFactor;
439 // must append this lightedModel to the list in the light.
440 lightContrib.TransformIterator[i]= pl->appendLightedModel(model);
442 // next light.
443 ligthSrcId++;
447 // Compute LightToMerge.
448 if(doMerge)
450 CRGBAF mergedAmbient(0,0,0);
451 uint j;
453 // For all lights in the lightList, merge Diffuse and Ambient term to mergedAmbient.
454 for(j=0;j<lightToMergeWeight.size();j++)
456 if(lightToMergeWeight[j] && lightList[j].Influence>0)
458 CPointLight *pl= lightList[j].PointLight;
460 // Get the original influence (ie for static PLs, biLinear influence)
461 float bkupInf= lightList[j].BkupInfluence;
462 // Get the attenuation of the pointLight to the model
463 float distToModel= lightList[j].DistanceToModel;
464 float attInf= pl->computeLinearAttenuation(modelPos, distToModel);
465 // Attenuate the color of the light by biLinear and distance/spot attenuation.
466 float lightInf= attInf * bkupInf;
467 // Modulate also by the percentage to merge
468 lightInf*= lightToMergeWeight[j];
469 // Add the full ambient term of the light, attenuated by light attenuation and WeightMerge
470 mergedAmbient.R+= pl->getAmbient().R * lightInf;
471 mergedAmbient.G+= pl->getAmbient().G * lightInf;
472 mergedAmbient.B+= pl->getAmbient().B * lightInf;
473 // Add 0.25f of the diffuse term of the light, attenuated by light attenuation and WeightMerge
474 // mul by 0.25f, because this is the averaged contribution of the diffuse part of a
475 // light to a sphere (do the maths...).
476 float f= lightInf*0.25f;
477 mergedAmbient.R+= pl->getDiffuse().R * f;
478 mergedAmbient.G+= pl->getDiffuse().G * f;
479 mergedAmbient.B+= pl->getDiffuse().B * f;
484 // Setup the merged Light
485 CRGBA amb;
486 sint v;
487 // Because of floating point error, it appears that sometime result may be slightly below 0.
488 // => clamp necessary
489 v= NLMISC::OptFastFloor(mergedAmbient.R); fastClamp8(v); amb.R= v;
490 v= NLMISC::OptFastFloor(mergedAmbient.G); fastClamp8(v); amb.G= v;
491 v= NLMISC::OptFastFloor(mergedAmbient.B); fastClamp8(v); amb.B= v;
492 amb.A = 255;
493 lightContrib.MergedPointLight= amb;
495 // Indicate we use the merged pointLight => the model must recompute lighting each frame
496 lightContrib.UseMergedPointLight= true;
498 else
500 // If the model is freezeHRC(), need to test each frame only if MergedPointLight is used.
501 lightContrib.UseMergedPointLight= false;
504 else
506 // point to end the list
507 i= startId;
510 // End the list.
511 if(i<NL3D_MAX_LIGHT_CONTRIBUTION)
513 lightContrib.PointLight[i]= NULL;
519 // ***************************************************************************
520 void CLightingManager::getDynamicPointLightList(const CVector &worldPos, std::vector<CPointLightInfluence> &lightList)
522 // For all quadGrids.
523 for(uint qgId=0; qgId<NL3D_QUADGRID_LIGHT_NUM_LEVEL; qgId++)
525 CQuadGrid<CPointLightInfo> &quadGrid= _LightQuadGrid[qgId];
527 // select the lights around this position in the quadGrids.
528 quadGrid.select(worldPos, worldPos);
530 // for all possible found lights
531 CQuadGrid<CPointLightInfo>::CIterator itLight;
532 for(itLight= quadGrid.begin(); itLight!=quadGrid.end(); itLight++)
534 // verify it includes the entity
535 if( (*itLight).Sphere.include(worldPos) )
537 // ok, insert in list.
538 CPointLightInfluence pli;
539 pli.PointLight= (*itLight).Light;
540 // No special Influence degradation scheme for Dynamic lighting
541 pli.Influence= 1;
542 lightList.push_back( pli );
550 } // NL3D