// 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/point_light.h"
#include "nel/3d/light.h"
#include "nel/3d/transform.h"
#include "nel/misc/common.h"
#include
using namespace NLMISC;
using namespace std;
namespace NL3D
{
// ***************************************************************************
//NLMISC::CBlockMemory CPointLight::_LightedModelListMemory(NL3D_LIGHTED_MODEL_ALLOC_BLOCKSIZE);
// ***************************************************************************
CPointLight::CPointLight() : _LightedModels(/*&_LightedModelListMemory*/)
{
_Position= CVector::Null;
_Ambient= CRGBA::Black;
_Diffuse= _Specular= CRGBA::White;
// Default setup. this is arbitrary
_AttenuationBegin= 10;
_AttenuationEnd= 30;
// Default spot setup. this is arbitrary
_SpotDirection.set(0,1,0);
_SpotAngleBegin= float(NLMISC::Pi/4);
_SpotAngleEnd= float(NLMISC::Pi/2);
// compute AttenuationFactors only one time.
static bool done= false;
static float cAtt, lAtt, qAtt;
static float spotCOOD, spotCAE, spotEXP;
if(!done)
{
done= true;
computeAttenuationFactors();
computeSpotAttenuationFactors();
// bkup setup.
cAtt= _ConstantAttenuation;
lAtt= _LinearAttenuation;
qAtt= _QuadraticAttenuation;
spotCAE= _CosSpotAngleEnd;
spotCOOD= _OOCosSpotAngleDelta;
spotEXP= _SpotExponent;
}
else
{
// just copy bkuped setup.
_ConstantAttenuation= cAtt;
_LinearAttenuation= lAtt;
_QuadraticAttenuation= qAtt;
_CosSpotAngleEnd= spotCAE;
_OOCosSpotAngleDelta= spotCOOD;
_SpotExponent= spotEXP;
}
_AddAmbientWithSun= false;
}
// ***************************************************************************
CPointLight::CPointLight(const CPointLight &o) : _LightedModels(/*&_LightedModelListMemory*/)
{
// copy (no need to init)
operator=(o);
}
// ***************************************************************************
CPointLight &CPointLight::operator=(const CPointLight &o)
{
/// copy all but _LightedModels !!
_Type= o._Type;
_Position= o._Position;
_Ambient= o._Ambient;
_Diffuse= o._Diffuse;
_Specular= o._Specular;
_AttenuationBegin= o._AttenuationBegin;
_AttenuationEnd= o._AttenuationEnd;
_OODeltaAttenuation= o._OODeltaAttenuation;
_ConstantAttenuation= o._ConstantAttenuation;
_LinearAttenuation= o._LinearAttenuation;
_QuadraticAttenuation= o._QuadraticAttenuation;
_SpotDirection= o._SpotDirection;
_SpotAngleBegin= o._SpotAngleBegin;
_SpotAngleEnd= o._SpotAngleEnd;
_CosSpotAngleEnd= o._CosSpotAngleEnd;
_OOCosSpotAngleDelta= o._OOCosSpotAngleDelta;
_SpotExponent= o._SpotExponent;
_AddAmbientWithSun= o._AddAmbientWithSun;
return *this;
}
// ***************************************************************************
CPointLight::~CPointLight()
{
resetLightedModels();
}
// ***************************************************************************
void CPointLight::setType(TType type)
{
_Type= type;
}
CPointLight::TType CPointLight::getType() const
{
return _Type;
}
// ***************************************************************************
void CPointLight::setupAttenuation(float attenuationBegin, float attenuationEnd)
{
// set values.
attenuationBegin= max(attenuationBegin, 0.f);
attenuationEnd= max(attenuationEnd, attenuationBegin);
_AttenuationBegin= attenuationBegin;
_AttenuationEnd= attenuationEnd;
// update factors.
computeAttenuationFactors();
}
// ***************************************************************************
void CPointLight::setupSpotAngle(float spotAngleBegin, float spotAngleEnd)
{
clamp(spotAngleBegin, 0.f, float(Pi));
clamp(spotAngleEnd, spotAngleBegin, float(Pi));
_SpotAngleBegin= spotAngleBegin;
_SpotAngleEnd= spotAngleEnd;
// update factors.
computeSpotAttenuationFactors();
}
// ***************************************************************************
void CPointLight::setupSpotDirection(const CVector &dir)
{
_SpotDirection= dir;
_SpotDirection.normalize();
}
// ***************************************************************************
void CPointLight::computeAttenuationFactors()
{
// disable attenuation?
if(_AttenuationBegin==0 && _AttenuationEnd==0)
{
// setup for attenuation disabled.
_ConstantAttenuation= 1;
_LinearAttenuation= 0;
_QuadraticAttenuation= 0;
}
else
{
// precompute attenuation values, with help of CLight formula!!
CLight dummyLight;
dummyLight.setupAttenuation(_AttenuationBegin, _AttenuationEnd);
_ConstantAttenuation= dummyLight.getConstantAttenuation();
_LinearAttenuation= dummyLight.getLinearAttenuation();
_QuadraticAttenuation= dummyLight.getQuadraticAttenuation();
// setup _OODeltaAttenuation
_OODeltaAttenuation= _AttenuationEnd - _AttenuationBegin;
if(_OODeltaAttenuation <=0 )
_OODeltaAttenuation= 0;
else
_OODeltaAttenuation= 1.0f / _OODeltaAttenuation;
}
}
// ***************************************************************************
void CPointLight::computeSpotAttenuationFactors()
{
// Factors for linear Attenuation.
float cosSpotAngleBegin= (float)cosf(_SpotAngleBegin);
_CosSpotAngleEnd= (float)cos(_SpotAngleEnd);
if(cosSpotAngleBegin - _CosSpotAngleEnd > 0)
_OOCosSpotAngleDelta= 1.0f / (cosSpotAngleBegin - _CosSpotAngleEnd);
else
_OOCosSpotAngleDelta= 1e10f;
// compute an exponent such that at middleAngle, att is 0.5f.
float caMiddle= (cosSpotAngleBegin + _CosSpotAngleEnd) /2;
float divid=(float)log (caMiddle);
if (divid==0.f)
divid=0.0001f;
_SpotExponent= (float)(log (0.5)/divid);
}
// ***************************************************************************
void CPointLight::serial(NLMISC::IStream &f)
{
sint ver= f.serialVersion(2);
if(ver>=2)
f.serial(_AddAmbientWithSun);
else
_AddAmbientWithSun= false;
if(ver>=1)
{
f.serialEnum(_Type);
f.serial(_SpotDirection);
f.serial(_SpotAngleBegin);
f.serial(_SpotAngleEnd);
}
else if(f.isReading())
{
_Type= PointLight;
_SpotDirection.set(0,1,0);
_SpotAngleBegin= float(NLMISC::Pi/4);
_SpotAngleEnd= float(NLMISC::Pi/2);
}
f.serial(_Position);
f.serial(_Ambient);
f.serial(_Diffuse);
f.serial(_Specular);
f.serial(_AttenuationBegin);
f.serial(_AttenuationEnd);
// precompute.
if(f.isReading())
{
computeAttenuationFactors();
computeSpotAttenuationFactors();
}
}
// ***************************************************************************
float CPointLight::computeLinearAttenuation(const CVector &pos) const
{
return computeLinearAttenuation(pos, (pos - _Position).norm() );
}
// ***************************************************************************
float CPointLight::computeLinearAttenuation(const CVector &pos, float dist, float modelRadius) const
{
float gAtt;
// Attenuation Distance
if(_AttenuationEnd==0)
gAtt= 1;
else
{
float distMinusRadius= dist - modelRadius;
if(distMinusRadius<_AttenuationBegin)
gAtt= 1;
else if(distMinusRadius<_AttenuationEnd)
{
gAtt= (_AttenuationEnd - distMinusRadius) * _OODeltaAttenuation;
}
else
gAtt= 0;
}
// Spot Attenuation
if(_Type== SpotLight)
{
float spotAtt;
// Compute unnormalized direction
CVector dir= pos - _Position;
// get cosAngle(dir, SpotDirection):
float cosAngleDirSpot= (dir*_SpotDirection) / dist;
// Modify with modelRadius. NB: made Only for big models.
if(modelRadius>0)
{
// If the pointLight is in the model, consider no spotAtt
if(modelRadius > dist)
spotAtt= 1;
else
{
// compute the angle of the cone made by the model sphere and the pointLightCenter.
float cosAngleSphere= modelRadius / sqrtf( sqr(dist) + sqr(modelRadius) );
/* If this one is smaller than cosAngleDirSpot, it's mean that the angle of this cone is greater than the
angleDirSpot, hence a part of the sphere "ps" exist such that _SportDirection*(ps-_Position).normed() == 1
=> no spotAttenuation
*/
if(cosAngleSphere < cosAngleDirSpot)
spotAtt= 1;
else
{
// Must compute cos( AngleDirSpot-AngleSphere )
float sinAngleSphere= sqrtf(1 - sqr(cosAngleSphere));
float sinAngleDirSpot= sqrtf(1 - sqr(cosAngleDirSpot));
float cosDelta= cosAngleSphere * cosAngleDirSpot + sinAngleSphere * sinAngleDirSpot;
// spot attenuation on the exterior of the sphere
spotAtt= (cosDelta - _CosSpotAngleEnd) * _OOCosSpotAngleDelta;
}
}
}
else
{
// spot attenuation
spotAtt= (cosAngleDirSpot - _CosSpotAngleEnd) * _OOCosSpotAngleDelta;
}
// modulate
clamp(spotAtt, 0.f, 1.f);
gAtt*= spotAtt;
}
return gAtt;
}
// ***************************************************************************
void CPointLight::setupDriverLight(CLight &light, uint8 factor)
{
// expand 0..255 to 0..256, to avoid loss of precision.
uint ufactor= factor + (factor>>7); // add 0 or 1.
// modulate with factor
CRGBA ambient, diffuse, specular;
ambient.modulateFromuiRGBOnly(_Ambient, ufactor);
diffuse.modulateFromuiRGBOnly(_Diffuse, ufactor);
specular.modulateFromuiRGBOnly(_Specular, ufactor);
// setup the pointLight
if(_Type == SpotLight )
{
light.setupSpotLight(ambient, diffuse, specular, _Position, _SpotDirection,
_SpotExponent, float(NLMISC::Pi/2) ,
_ConstantAttenuation, _LinearAttenuation, _QuadraticAttenuation);
}
// PointLight or AmbientLight
else
{
light.setupPointLight(ambient, diffuse, specular, _Position, CVector::Null,
_ConstantAttenuation, _LinearAttenuation, _QuadraticAttenuation);
}
}
// ***************************************************************************
void CPointLight::setupDriverLightUserAttenuation(CLight &light, uint8 factor)
{
// expand 0..255 to 0..256, to avoid loss of precision.
uint ufactor= factor + (factor>>7); // add 0 or 1.
// modulate with factor
CRGBA ambient, diffuse, specular;
ambient.modulateFromuiRGBOnly(_Ambient, ufactor);
diffuse.modulateFromuiRGBOnly(_Diffuse, ufactor);
specular.modulateFromuiRGBOnly(_Specular, ufactor);
// setup the pointLight, disabling attenuation.
// NB: setup a pointLight even if it is a SpotLight because already attenuated
light.setupPointLight(ambient, diffuse, specular, _Position, CVector::Null,
1, 0, 0);
}
// ***************************************************************************
void CPointLight::resetLightedModels()
{
// For each transform, resetLighting him.
while(_LightedModels.begin() != _LightedModels.end() )
{
CTransform *model= *_LightedModels.begin();
// reset lighting
model->resetLighting();
// NB: the transform must erase him from this list.
nlassert( _LightedModels.begin() == _LightedModels.end() || *_LightedModels.begin() != model );
}
}
// ***************************************************************************
CPointLight::ItTransformList CPointLight::appendLightedModel(CTransform *model)
{
// append the entry in the list
_LightedModels.push_back(model);
ItTransformList it= _LightedModels.end();
it--;
return it;
}
// ***************************************************************************
void CPointLight::removeLightedModel(ItTransformList it)
{
// delete the entry in the list.
_LightedModels.erase(it);
}
// ***************************************************************************
void CPointLight::purge ()
{
//_LightedModelListMemory.purge();
}
// ***************************************************************************
void CPointLight::setAddAmbientWithSun(bool state)
{
_AddAmbientWithSun= state;
}
} // NL3D