// Ryzom - 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 "stdpch.h"
/////////////
// INCLUDE //
/////////////
// 3D Interface
#include "nel/3d/u_instance.h"
#include "nel/3d/u_scene.h"
#include "nel/3d/u_water.h"
#include "nel/3d/u_landscape.h"
// Client.
#include "light_cycle_manager.h"
#include "client_cfg.h"
#include "ig_client.h"
#include "user_entity.h"
#include "pacs_client.h"
#include "world_database_manager.h"
#include "continent_manager.h"
#include "continent.h"
#include "ig_enum.h"
#include "ig_callback.h"
#include "pacs_client.h"
#include "sound_manager.h"
#include "weather_manager_client.h"
#include "misc.h"
#include "interface_v3/interface_manager.h"
//#include "sound_manager.h" // \todo GUIGUI : uncomment after new FE done and class modified
H_AUTO_DECL(RZ_LightCycleManager)
///////////
// USING //
///////////
using namespace NLMISC;
using namespace NL3D;
using namespace NLPACS;
using namespace std;
////////////
// EXTERN //
////////////
extern UDriver *Driver;
extern UScene *Scene;
extern UScene *SceneRoot;
extern EGSPD::CSeason::TSeason CurrSeason;
////////////
// EXTERN //
////////////
extern ULandscape *Landscape;
static uint diffColors(CRGBA c1, CRGBA c2)
{
return (uint) maxof(abs((sint) c1.R - c2.R), abs((sint) c1.G - c2.G), abs((sint) c1.B - c2.B));
}
//-----------------------------------------------
float CLightCycleDesc::getNightTransitionLength() const
{
H_AUTO_USE(RZ_LightCycleManager)
return NightTransitionEndHour >= NightTransitionStartHour ? NightTransitionEndHour - NightTransitionStartHour
: NightTransitionEndHour + NumHours - NightTransitionStartHour;
}
//-----------------------------------------------
float CLightCycleDesc::getDawnTransitionLength() const
{
H_AUTO_USE(RZ_LightCycleManager)
return DawnTransitionEndHour >= DawnTransitionStartHour ? DawnTransitionEndHour - DawnTransitionStartHour
: DawnTransitionEndHour + NumHours - DawnTransitionStartHour;
}
//-----------------------------------------------
CLightCycleManager::CLightCycleManager() :
_Hour(0),
_Touched(true),
_ValidDesc(false),
_LightLevel(0),
_WeatherLighting(0),
_LastWeatherLighting(0),
_UpdateFreq(0),
_State(StateUnknown),
_PrevState(StateUnknown)
{
H_AUTO_USE(RZ_LightCycleManager)
}
//-----------------------------------------------
void CLightCycleManager::getLandscapeLightColor(NLMISC::CRGBA &diffuse, NLMISC::CRGBA &ambiant)
{
H_AUTO_USE(RZ_LightCycleManager)
nlassert(_LightLevel >= 0 && _LightLevel <= 1.f);
if(ContinentMngr.cur())
{
switch(_State)
{
case DayToNight:
if (_LightLevel <= _Desc.DuskRatio)
{
// day->dusk
uint level = (uint) (256.f * (_Desc.DuskRatio != 0.f ? _LightLevel / _Desc.DuskRatio : 0.f));
diffuse.blendFromui(ContinentMngr.cur()->LandscapeLightDay.Diffuse, ContinentMngr.cur()->LandscapeLightDusk.Diffuse, level);
ambiant.blendFromui(ContinentMngr.cur()->LandscapeLightDay.Ambiant, ContinentMngr.cur()->LandscapeLightDusk.Ambiant, level);
}
else
{
// dusk->night
uint level = (uint) (256.f * (_Desc.DuskRatio != 1.f ? (_LightLevel - _Desc.DuskRatio) / (1.f - _Desc.DuskRatio) : 0.f));
diffuse.blendFromui(ContinentMngr.cur()->LandscapeLightDusk.Diffuse, ContinentMngr.cur()->LandscapeLightNight.Diffuse, level);
ambiant.blendFromui(ContinentMngr.cur()->LandscapeLightDusk.Ambiant, ContinentMngr.cur()->LandscapeLightNight.Ambiant, level);
}
break;
default:
uint level = (uint) (256.f * _LightLevel);
diffuse.blendFromui(ContinentMngr.cur()->LandscapeLightDay.Diffuse, ContinentMngr.cur()->LandscapeLightNight.Diffuse, level);
ambiant.blendFromui(ContinentMngr.cur()->LandscapeLightDay.Ambiant, ContinentMngr.cur()->LandscapeLightNight.Ambiant, level);
break;
}
}
}
//-----------------------------------------------
bool CLightCycleManager::setLightDesc(const CLightCycleDesc &desc)
{
H_AUTO_USE(RZ_LightCycleManager)
if (desc.NightTransitionStartHour > desc.NumHours)
{
nlwarning ("NightTransitionStartHour (%d) > NumHours (%d)", desc.NightTransitionStartHour, desc.NumHours);
_State = StateUnknown;
return false;
}
if (desc.NightTransitionEndHour > desc.NumHours)
{
nlwarning ("NightTransitionEndHour (%d) > NumHours (%d)", desc.NightTransitionEndHour, desc.NumHours);
_State = StateUnknown;
return false;
}
//
if (desc.DawnTransitionStartHour > desc.NumHours)
{
nlwarning ("DawnTransitionStartHour (%d) > NumHours (%d)", desc.DawnTransitionStartHour, desc.NumHours);
_State = StateUnknown;
return false;
}
if (desc.DawnTransitionEndHour > desc.NumHours)
{
nlwarning ("DawnTransitionEndHour (%d) > NumHours (%d)", desc.DawnTransitionEndHour, desc.NumHours);
_State = StateUnknown;
return false;
}
//
// test if intervals are not overlapping
if (!(desc.DawnTransitionStartHour >= desc.NightTransitionEndHour
|| desc.DawnTransitionEndHour <= desc.NightTransitionStartHour
)
)
{
nlwarning("!(DawnTransitionStartHour (%d) >= NightTransitionEndHour (%d) || DawnTransitionEndHour (%d) <= NightTransitionStartHour (%d))",
desc.DawnTransitionStartHour, desc.NightTransitionEndHour, desc.DawnTransitionEndHour, desc.NightTransitionStartHour);
_State = StateUnknown;
return false;
}
//
//
_Desc = desc;
_ValidDesc = true;
_Hour = 0;
updateState();
return true;
}
//-----------------------------------------------
float CLightCycleManager::getUpdateDuration() const
{
H_AUTO_USE(RZ_LightCycleManager)
if (!_ValidDesc || _UpdateFreq == 0.f) return 0.f;
return (1.f / _UpdateFreq) / _Desc.RealDayLength * _Desc.NumHours;
}
//-----------------------------------------------
std::string CLightCycleManager::getStateString() const
{
H_AUTO_USE(RZ_LightCycleManager)
switch(_State)
{
case NightToDay: return "night->day";
case DayToNight: return "day->night";
case Day: return "day";
case Night: return "night";
default:
return "???";
}
return "???";
}
//-----------------------------------------------
void CLightCycleManager::updateState()
{
H_AUTO_USE(RZ_LightCycleManager)
_PrevState = _State;
if(isInInterval(_Desc.DawnTransitionStartHour, _Desc.DawnTransitionEndHour, _Hour))
{
// night->day
_State = NightToDay;
return;
}
else if(isInInterval(_Desc.DawnTransitionEndHour, _Desc.NightTransitionStartHour, _Hour))
{
// day
_State = Day;
return;
}
else if(isInInterval(_Desc.NightTransitionStartHour, _Desc.NightTransitionEndHour, _Hour))
{
// day->night
_State = DayToNight;
return;
}
else if(isInInterval(_Desc.NightTransitionEndHour, _Desc.DawnTransitionStartHour, _Hour))
{
// night
if( _PrevState != Night && _PrevState != StateUnknown)
{
CInterfaceManager * pIM = CInterfaceManager::getInstance();
if( pIM )
{
CAHManager::getInstance()->runActionHandler("set",NULL,"dblink=UI:VARIABLES:NIGHT_WARNING_WANTED|value=1");
}
}
_State = Night;
return;
}
_State = StateUnknown;
return;
}
//
//-----------------------------------------------
bool CLightCycleManager::isInTransition() const
{
H_AUTO_USE(RZ_LightCycleManager)
return isInInterval(_Desc.DawnTransitionStartHour, _Desc.DawnTransitionEndHour, _Hour)
|| isInInterval(_Desc.NightTransitionStartHour, _Desc.DawnTransitionEndHour, _Hour);
}
//-----------------------------------------------
void CLightCycleManager::setHour(float hour, const CWeatherManagerClient &wm, NLMISC::CRGBA lightningColor)
{
H_AUTO_USE(RZ_LightCycleManager)
if (!_ValidDesc) return;
if (hour < 0) hour = 0;
if (hour > _Desc.NumHours)
{
hour -= ::floorf(hour / _Desc.NumHours) * _Desc.NumHours;
}
float updateDuration = getUpdateDuration();
if (fabs(_Hour - hour) >= (4.f * updateDuration))
{
touch();
}
_Hour = hour;
_LightLevel = getLightLevel(hour);
clamp(_LightLevel, 0.f, 1.f); // should not be necessary, but we avoid imprecisions
updateState();
_WeatherLighting = wm.getCurrWeatherState().Lighting;
// Change water lighting
NL3D::UWaterHeightMapManager::setBlendFactor(Driver, _LightLevel);
if (_PrevState != _State)
{
if (_State == Night || _State == Day)
{
// a transiation has ended, so release textures used for the blend
NL3D::UWaterHeightMapManager::releaseBlendTextures();
}
}
// directionnal lights
// (override the global light by the light in the weather manager : during bad weather, the scene can be darker)
setupCanopyLight(_WeatherLighting);
setupMainLight(_WeatherLighting);
// landscape lighting
CRGBA diffuse, ambiant;
getLandscapeLightColor(diffuse, ambiant);
bool colorTouched = diffuse != _LastDiffuse || ambiant != _LastAmbient;
if (colorTouched)
{
_LastDiffuse = diffuse;
_LastAmbient = ambiant;
// change landscape
if (Landscape)
{
if(ContinentMngr.cur())
Landscape->setupStaticLight(diffuse, ambiant, ContinentMngr.cur()->StaticLightingFactor[CurrSeason]);
else
Landscape->setupStaticLight(diffuse, ambiant, 1.f);
_UpdateFreq = getLandscapePatchUpdateFreq();
if (_Touched)
{
Landscape->updateLightingAll();
Landscape->setUpdateLightingFrequency(0);
}
else
{
Landscape->setUpdateLightingFrequency(_UpdateFreq);
}
}
}
_Touched = false;
// Set the Sun color only if not indoor
if (ContinentMngr.cur()->Indoor)
{
Scene->setSunAmbient(CRGBA(150, 150, 150, 255));
}
else
{
CRGBA color;
color.add(_LastDiffuse, lightningColor);
Scene->setLightGroupColor (LightGroupDay, color);
float nightLevel = _LightLevel*255.f;
clamp (nightLevel, 0, 255);
color.set ((uint8)nightLevel, (uint8)nightLevel, (uint8)nightLevel);
Scene->setLightGroupColor (LightGroupNight, color);
}
if (Landscape)
{
if (!isInInterval(_Desc.DawnTransitionStartHour, _Desc.DawnTransitionEndHour + 4.f * updateDuration, hour)
&& !isInInterval(_Desc.NightTransitionStartHour, _Desc.NightTransitionEndHour + 4.f * updateDuration, hour)
)
{
Landscape->setUpdateLightingFrequency(0);
}
}
/* if (SoundMngr)
{
float ratio;
if(isInDayInterval(_Desc.DawnTransitionStartHour, _Desc.DawnTransitionEndHour, _Desc.NumHours, hour, ratio))
{
// night->day
SoundMngr->setDayNightRatio (1.0f-ratio);
}
else if(isInDayInterval(_Desc.DawnTransitionEndHour, _Desc.NightTransitionStartHour, _Desc.NumHours, hour, ratio))
{
// day
SoundMngr->setDayNightRatio (0.0f);
}
else if(isInDayInterval(_Desc.NightTransitionStartHour, _Desc.NightTransitionEndHour, _Desc.NumHours, hour, ratio))
{
// day->night
SoundMngr->setDayNightRatio (ratio);
}
else if(isInDayInterval(_Desc.NightTransitionEndHour, _Desc.DawnTransitionStartHour, _Desc.NumHours, hour, ratio))
{
// night
SoundMngr->setDayNightRatio (1.0f);
}
}
*/
}
//-----------------------------------------------
// NB : interval can be reversed
bool CLightCycleManager::isInInterval(float start, float end, float value)
{
H_AUTO_USE(RZ_LightCycleManager)
return start <= end ? value >= start && value < end
: value >= start || value < end;
}
//-----------------------------------------------
// NB : interval can be reversed
bool CLightCycleManager::isInDayInterval(float startHour, float endHour, float dayDuration, float hour, float &ratio)
{
H_AUTO_USE(RZ_LightCycleManager)
if (startHour <= endHour)
{
if (hour >= startHour && hour < endHour)
{
ratio = startHour != endHour ? (hour - startHour) / (endHour - startHour)
: 0;
return true;
}
else
return false;
}
else
{
if (hour >= startHour || hour < endHour)
{
ratio = hour >= startHour ? (hour - startHour) / (dayDuration - startHour + endHour)
: (hour + dayDuration - startHour) / (dayDuration - startHour + endHour);
return true;
}
else
return false;
}
}
//-----------------------------------------------
float CLightCycleManager::getLightLevel(float hour) const
{
H_AUTO_USE(RZ_LightCycleManager)
float lightValue = 0.f;
if (isInDayInterval(_Desc.NightTransitionStartHour, _Desc.NightTransitionEndHour, _Desc.NumHours, hour, lightValue))
return lightValue;
if (isInDayInterval(_Desc.DawnTransitionStartHour, _Desc.DawnTransitionEndHour, _Desc.NumHours, hour, lightValue))
return 1.f - lightValue;
// No transition, it is night or day
if (_Desc.DawnTransitionEndHour <= _Desc.NightTransitionStartHour)
{
return hour >= _Desc.DawnTransitionEndHour && hour < _Desc.NightTransitionStartHour ? 0.f : 1.f;
}
else
{
return hour >= _Desc.DawnTransitionEndHour || hour < _Desc.NightTransitionStartHour ? 0.f : 1.f;
}
}
//-----------------------------------------------
void CLightCycleManager::create()
{
H_AUTO_USE(RZ_LightCycleManager)
Driver->enableLight(0, true);
}
//-----------------------------------------------
void CLightCycleManager::setDirLight(const CDirLightSetup &setup0, const CDirLightSetup &setup1, float level, float intensity,NL3D::UScene &scene)
{
H_AUTO_USE(RZ_LightCycleManager)
CDirLightSetup resultSetup;
resultSetup.blend(setup0, setup1, level);
resultSetup.modulate(intensity);
//
scene.setSunAmbient (resultSetup.Ambiant);
scene.setSunDiffuse (resultSetup.Diffuse);
scene.setSunSpecular (resultSetup.Specular);
scene.setSunDirection(resultSetup.Direction);
}
//-----------------------------------------------
float CLightCycleManager::getLandscapePatchUpdateFreq() const
{
H_AUTO_USE(RZ_LightCycleManager)
if(!_ValidDesc)
return 0.f;
if(ContinentMngr.cur() == 0)
return 0.f;
// transition duration in seconds
float dt = std::min(_Desc.getDawnTransitionLength(), _Desc.getNightTransitionLength()) * _Desc.RealDayLength / _Desc.NumHours;
uint numStep = std::max(diffColors(ContinentMngr.cur()->LandscapeLightDay.Diffuse, ContinentMngr.cur()->LandscapeLightNight.Diffuse),
diffColors(ContinentMngr.cur()->LandscapeLightDay.Ambiant, ContinentMngr.cur()->LandscapeLightNight.Ambiant)
);
numStep = std::min(numStep, _Desc.MaxNumColorSteps);
return (float) numStep / dt;
}
//-----------------------------------------------
void CLightCycleManager::touch()
{
H_AUTO_USE(RZ_LightCycleManager)
_Touched = true;
}
//-----------------------------------------------
void CLightCycleManager::instanceGroupAdded(NL3D::UInstanceGroup * /* ig */)
{
H_AUTO_USE(RZ_LightCycleManager)
}
//-----------------------------------------------
// rootLight :
// Set the light to render the root.
//-----------------------------------------------
void CLightCycleManager::setupCanopyLight(float intensity)
{
H_AUTO_USE(RZ_LightCycleManager)
if(!SceneRoot)
return;
if(ContinentMngr.cur())
{
switch (_State)
{
case DayToNight: // blend form day to night with dusk transition
setupDayToNightLight(*SceneRoot, ContinentMngr.cur()->RootLightDay, ContinentMngr.cur()->RootLightDusk, ContinentMngr.cur()->RootLightNight, intensity);
break;
default: // blend from night to day with no other transition
setDirLight(ContinentMngr.cur()->RootLightDay, ContinentMngr.cur()->RootLightNight, _LightLevel, intensity, *SceneRoot);
break;
}
}
}// setupCanopyLight //
//-----------------------------------------------
// mainLight :
// Set the light to render the main scene
//-----------------------------------------------
void CLightCycleManager::setupMainLight(float intensity)
{
H_AUTO_USE(RZ_LightCycleManager)
if(!Scene)
return;
if(ContinentMngr.cur())
{
switch (_State)
{
case DayToNight: // blend form day to night with dusk transition
setupDayToNightLight(*Scene, ContinentMngr.cur()->EntityLightDay, ContinentMngr.cur()->EntityLightDusk, ContinentMngr.cur()->EntityLightNight, intensity);
break;
default: // blend from night to day with no other transition
setDirLight(ContinentMngr.cur()->EntityLightDay, ContinentMngr.cur()->EntityLightNight, _LightLevel, intensity, *Scene);
break;
}
}
}// setupMainLight //
//-----------------------------------------------
void CLightCycleManager::setupDayToNightLight(NL3D::UScene &scene, const CDirLightSetup &dayLight, const CDirLightSetup &duskLight, const CDirLightSetup &nightLight, float lightIntensity)
{
H_AUTO_USE(RZ_LightCycleManager)
float blendFactor;
if (_LightLevel <= _Desc.DuskRatio)
{
blendFactor = _Desc.DuskRatio != 0 ? _LightLevel / _Desc.DuskRatio : 0.f;
setDirLight(dayLight,
duskLight,
blendFactor,
lightIntensity,
scene
);
}
else
{
blendFactor = _Desc.DuskRatio != 1.f ? (_LightLevel - _Desc.DuskRatio) / (1.f - _Desc.DuskRatio) : 0.f;
setDirLight(duskLight,
nightLight,
blendFactor,
lightIntensity,
scene
);
}
}