/*
** Command & Conquer Generals Zero Hour(tm)
** Copyright 2025 Electronic Arts Inc.
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU 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 General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see .
*/
/***********************************************************************************************
*** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ***
***********************************************************************************************
* *
* Project Name : WWPhys *
* *
* $Archive:: /Commando/Code/ww3d2/lightenvironment.cpp $*
* *
* Original Author:: Greg Hjelstrom *
* *
* $Author:: Greg_h $*
* *
* $Modtime:: 2/01/01 5:40p $*
* *
* $Revision:: 3 $*
* *
*---------------------------------------------------------------------------------------------*
* Functions: *
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
#include "lightenvironment.h"
#include "matrix3d.h"
#include "camera.h"
#include "light.h"
#include "colorspace.h"
/*
** Constants
*/
const float DIFFUSE_TO_AMBIENT_FRACTION = 1.0f;
/*
** Static variables
*/
static _LightingLODCutoff = 0.5f;
static _LightingLODCutoff2 = 0.5f * 0.5f;
/************************************************************************************************
**
** LightEnvironmentClass::InputLightStruct Implementation
**
************************************************************************************************/
void LightEnvironmentClass::InputLightStruct::Init
(
const LightClass & light,
const Vector3 & object_center
)
{
m_point = false;
switch(light.Get_Type())
{
case LightClass::POINT:
case LightClass::SPOT:
Init_From_Point_Or_Spot_Light(light,object_center);
break;
case LightClass::DIRECTIONAL:
Init_From_Directional_Light(light,object_center);
break;
};
}
void LightEnvironmentClass::InputLightStruct::Init_From_Point_Or_Spot_Light
(
const LightClass & light,
const Vector3 & object_center
)
{
/*
** Compute the direction vector and the distance to the light
*/
Direction = light.Get_Position() - object_center;
float dist = Direction.Length();
if (dist > 0.0f) {
Direction /= dist;
}
/*
** Compute the attenuation factor
*/
float atten = 1.0f;
double atten_start,atten_end;
light.Get_Far_Attenuation_Range(atten_start,atten_end);
if (light.Get_Flag(LightClass::FAR_ATTENUATION)) {
if (WWMath::Fabs(atten_end - atten_start) < WWMATH_EPSILON) {
/*
** Start and end are equal, attenuation is a "step" function
*/
if (dist > atten_start) {
atten = 0.0f;
}
} else {
/*
** Compute the attenuation
*/
atten = 1.0f - (dist - atten_start) / (atten_end - atten_start);
atten = WWMath::Clamp(atten,0.0f,1.0f);
}
}
if (light.Get_Type() == LightClass::SPOT) {
Vector3 spot_dir;
light.Get_Spot_Direction(spot_dir);
Matrix3D::Rotate_Vector(light.Get_Transform(),spot_dir,&spot_dir);
float spot_angle_cos = light.Get_Spot_Angle_Cos();
atten *= (Vector3::Dot_Product(-spot_dir,Direction) - spot_angle_cos) / (1.0f - spot_angle_cos);
atten = WWMath::Clamp(atten,0.0f,1.0f);
}
/*
** Compute the ambient and diffuse values. Rejecting the diffuse
** component and folding it into the ambient component if it is below
** the LOD cutoff
*/
light.Get_Ambient(&Ambient);
light.Get_Diffuse(&Diffuse);
Ambient *= light.Get_Intensity(); //(gth) CNC3 obey the intensity parameter
Diffuse *= light.Get_Intensity();
m_point = (light.Get_Type() == LightClass::POINT);
m_center = light.Get_Position();
m_innerRadius = atten_start;
m_outerRadius = atten_end;
m_ambient = Ambient;
m_diffuse = Diffuse;
if (Diffuse.Length2() > _LightingLODCutoff2) {
DiffuseRejected = false;
Ambient *= atten;
Diffuse *= atten;
} else {
DiffuseRejected = true;
Ambient *= atten;
Ambient += atten * DIFFUSE_TO_AMBIENT_FRACTION * Diffuse;
Diffuse.Set(0,0,0);
}
}
void LightEnvironmentClass::InputLightStruct::Init_From_Directional_Light
(
const LightClass & light,
const Vector3 & object_center
)
{
Direction = -light.Get_Transform().Get_Z_Vector();
DiffuseRejected = false;
light.Get_Ambient(&Ambient);
light.Get_Diffuse(&Diffuse);
}
float LightEnvironmentClass::InputLightStruct::Contribution(void)
{
return Diffuse.Length2();
}
/************************************************************************************************
**
** LightEnvironmentClass::OutputLightStruct Implementation
**
************************************************************************************************/
void LightEnvironmentClass::OutputLightStruct::Init
(
const InputLightStruct & input,
const Matrix3D & camera_tm
)
{
Diffuse = input.Diffuse;
Matrix3D::Inverse_Rotate_Vector(camera_tm,input.Direction,&Direction);
// Guard against a direction that is invalid
if(Direction.Length2() == 0.0f) {
Direction.X = 1.0f;
}
}
/************************************************************************************************
**
** LightEnvironmentClass Implementation
**
************************************************************************************************/
LightEnvironmentClass::LightEnvironmentClass(void) :
LightCount(0),
ObjectCenter(0,0,0),
OutputAmbient(0,0,0),
FillLight(),
FillIntensity(0.0f)
{
}
LightEnvironmentClass::~LightEnvironmentClass(void)
{
}
void LightEnvironmentClass::Reset(const Vector3 & object_center,const Vector3 & ambient)
{
LightCount = 0;
ObjectCenter = object_center;
OutputAmbient = ambient;
}
void LightEnvironmentClass::Add_Light(const LightClass & light)
{
// Jani: Don't accept lights that are almost black
Vector3 diff;
light.Get_Diffuse(&diff);
if (diff[0]<0.05f && diff[1]<0.05f && diff[2]<0.05f) {
return;
}
/*
** Compute the equivalent directional + ambient light
*/
InputLightStruct new_light;
new_light.Init(light, ObjectCenter);
// If we have the fill light set, we also want to the diffuse light to be modified by the intensity of the light source
if(FillIntensity) new_light.Diffuse *= light.Get_Intensity();
/*
** Add in the ambient component
*/
OutputAmbient += new_light.Ambient;
/*
** If not rejected, add the directional component to the active lights
*/
if (new_light.DiffuseRejected == false || new_light.m_point) {
// Insert the light into the sorted list of InputLights if it's contribution is greater than the any of the current number of lights
for (int light_index=0; light_index < LightCount; light_index++) {
if (new_light.Contribution() > InputLights[light_index].Contribution()) {
// Move back the lights in the InputLights Array to make space for the new light.
// The last light might be discarded if it moves off the array as it is the weakest light in the list.
for (int i = LightCount; i > light_index; --i) {
if (i < MAX_LIGHTS) {
InputLights[i] = InputLights[i - 1];
}
}
// Add the new light into the InputLights List where it belongs
InputLights[light_index] = new_light;
// Increment the light count if we have not reach the maximum lights limit yet
LightCount = min(LightCount + 1, (int)MAX_LIGHTS);
// Since we have inserted a new light, we are done for this function
return;
}
}
// If the light was not inserted but there are still spots empty in the InputLights list, insert the lights at the end of the list
if (LightCount < MAX_LIGHTS) {
InputLights[LightCount] = new_light;
++LightCount;
}
}
}
void LightEnvironmentClass::Pre_Render_Update(const Matrix3D & camera_tm)
{
/*
** Calculate and set up the fill light for the object
*/
Calculate_Fill_Light();
/*
** Transform each light into camera space
** and add up the ambient effect of each light
*/
for (int light_index=0; light_index 360.0f) {
temp.X -= 360.0f;
}
temp.Z *= FillIntensity; // fraction of the intensity
HSV_To_RGB(FillLight.Diffuse, temp);
// Zero out the fill ambient
FillLight.Ambient.Set(0.0f, 0.0f, 0.0f);
// now we set the fill light direction to be opposite the average light
FillLight.Direction = average_light.Direction * (-1.0f);
FillLight.DiffuseRejected = false;
// Add the fill light into the InputLights list
Add_Fill_Light();
}