Files
Goober5000andClaude Opus 5.5 571e63d659 value-initialize lights when adding them
Directional, point, and cone lights left local_vec, local_vec2, and some of
the type-specific fields uninitialized until something rotated or set them.
The sun copies in Static_light were never rotated at all until the previous
commit, so light_apply_rgb's sun term read uninitialized memory.  Zero every
field up front, which also covers what the tube light did by hand.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 00:17:02 -04:00

729 lines
19 KiB
C++

/*
* Copyright (C) Volition, Inc. 1999. All rights reserved.
*
* All source code herein is the property of Volition, Inc. You may not sell
* or otherwise commercially exploit the source or things you created based on the
* source.
*
*/
#include "cmdline/cmdline.h"
#include "debugconsole/console.h"
#include "globalincs/systemvars.h"
#include "graphics/2d.h"
#include "graphics/color.h"
#include "graphics/light.h"
#include "lighting/lighting.h"
#include "lighting/lighting_profiles.h"
#include "math/vecmat.h"
#include "model/modelrender.h"
#include "render/3d.h"
#include "options/Option.h"
SCP_vector<light> Lights;
SCP_vector<light> Static_light;
static int Light_in_shadow = 0; // If true, this means we're in a shadow
#define MIN_LIGHT 0.03f // When light drops below this level, ignore it. Must be non-zero! (1/32)
static int Lighting_off = 0;
// For lighting values, 0.75 is full intensity
#define AMBIENT_LIGHT_DEFAULT 0.15f //0.10f
#define REFLECTIVE_LIGHT_DEFAULT 0.75f //0.90f
static float Ambient_light = AMBIENT_LIGHT_DEFAULT;
static float Reflective_light = REFLECTIVE_LIGHT_DEFAULT;
int Lighting_flag = 1;
int Num_lights = 0;
lighting_mode Lighting_mode = lighting_mode::NORMAL;
DCF(light,"Changes lighting parameters")
{
SCP_string arg_str;
float val_f;
bool val_b;
if (dc_optional_string_either("help", "--help")) {
dc_printf( "Usage: light keyword\nWhere keyword can be in the following forms:\n" );
dc_printf( "light on|off Turns all lighting on/off\n" );
dc_printf( "light default Resets lighting to all default values\n" );
dc_printf( "light ambient X Where X is the ambient light between 0 and 1.0\n" );
dc_printf( "light reflect X Where X is the material reflectiveness between 0 and 1.0\n" );
dc_printf( "light dynamic [bool] Toggles dynamic lighting on/off\n" );
return;
}
if (dc_optional_string_either("status", "--status") || dc_optional_string_either("?", "--?")) {
dc_printf( "Ambient light is set to %.2f\n", Ambient_light );
dc_printf( "Reflective light is set to %.2f\n", Reflective_light );
dc_printf( "Dynamic lighting is: %s\n", (Lighting_flag?"on":"off") );
return;
}
if (dc_optional_string("ambient")) {
dc_stuff_float(&val_f);
if ((val_f < 0.0f) || (val_f > 1.0f)) {
dc_printf(" Error: ambient value must be between 0.0 and 1.0\n");
} else {
Ambient_light = val_f;
}
} else if (dc_optional_string("reflect")) {
dc_stuff_float(&val_f);
if ( (val_f < 0.0f) || (val_f > 1.0f)) {
dc_printf(" Error: reflect value mus be between 0.0 and 1.0\n");
} else {
Reflective_light = val_f;
}
} else if (dc_optional_string("default")) {
Ambient_light = AMBIENT_LIGHT_DEFAULT;
Reflective_light = REFLECTIVE_LIGHT_DEFAULT;
Lighting_flag = 0;
} else if (dc_optional_string("dynamic")) {
dc_stuff_boolean(&val_b);
Lighting_flag = val_b;
} else if(dc_maybe_stuff_boolean(&Lighting_off)) {
Lighting_off = !Lighting_off;
} else {
dc_stuff_string_white(arg_str);
dc_printf("Error: Unknown argument '%s'\n", arg_str.c_str());
}
}
// used by In-Game Options menu
static bool DeferredLightingEnabled = true;
static void parse_deferred_lighting_func()
{
bool enabled;
stuff_boolean(&enabled);
DeferredLightingEnabled = enabled;
}
// coverity[GLOBAL_INIT_ORDER] -- safe; OptionBuilder::finish() uses Meyers singleton
static auto DeferredLightingOption = options::OptionBuilder<bool>("Graphics.DeferredLighting",
std::pair<const char*, int>{"Deferred Lighting", 1782},
std::pair<const char*, int>{"Enables or disables deferred lighting", 1783})
.category(std::make_pair("Graphics", 1825))
.default_func([]() { return DeferredLightingEnabled;})
.level(options::ExpertLevel::Advanced)
.bind_to_once(&DeferredLightingEnabled)
.importance(60)
.parser(parse_deferred_lighting_func)
.finish();
static bool DeferredCockpitLightingEnabled = true;
static void parse_deferredcockpit_lighting_func()
{
bool enabled;
stuff_boolean(&enabled);
DeferredCockpitLightingEnabled = enabled;
}
// coverity[GLOBAL_INIT_ORDER] -- safe; OptionBuilder::finish() uses Meyers singleton
static auto DeferredCockpitLightingOption = options::OptionBuilder<bool>("Graphics.DeferredCockpitLighting",
std::pair<const char*, int>{"Deferred Cockpit Lighting", 1864},
std::pair<const char*, int>{"Enables or disables deferred lighting in cockpits (requires Deferred Lighting to be enabled)", 1865})
.category(std::make_pair("Graphics", 1825))
.default_func([]() { return DeferredCockpitLightingEnabled;})
.level(options::ExpertLevel::Advanced)
.bind_to_once(&DeferredCockpitLightingEnabled)
.importance(60)
.parser(parse_deferredcockpit_lighting_func)
.finish();
bool light_deferred_enabled()
{
if (Using_in_game_options) {
static bool isToggledOn = DeferredLightingOption->getValue();
// This used to get the value of the option object itself,
// however that is not a free operation and changing it requires a restart anyway
// if the restart requirement is lifted care should be taken to cache this value
// and never look it up more than once a frame
// otherwise the performance footprint is measurable enough to worry about.
return isToggledOn;
} else {
return !Cmdline_no_deferred_lighting;
}
}
bool light_deferredcockpit_enabled()
{
if (Using_in_game_options) {
static bool isToggledOn = DeferredCockpitLightingOption->getValue();
// Note, the above comment within light_deferred_enabled()
// about caching the value also applies here.
return isToggledOn;
} else {
return Cmdline_deferred_lighting_cockpit;
}
}
void light_reset()
{
Static_light.clear();
Lights.clear();
Num_lights = 0;
}
extern vec3d Object_position;
/**
* Rotates the light into the current frame of reference
*
* @param l Light to rotate
*/
static void light_rotate(light * l)
{
switch( l->type ) {
case Light_Type::Directional:
// Rotate the light direction into local coodinates
vm_vec_rotate(&l->local_vec, &l->vec, &Light_matrix );
break;
case Light_Type::Point: {
vec3d tempv;
// Rotate the point into local coordinates
vm_vec_sub(&tempv, &l->vec, &Light_base );
vm_vec_rotate(&l->local_vec, &tempv, &Light_matrix );
}
break;
case Light_Type::Tube:{
vec3d tempv;
// Rotate the point into local coordinates
vm_vec_sub(&tempv, &l->vec, &Light_base );
vm_vec_rotate(&l->local_vec, &tempv, &Light_matrix );
// Rotate the point into local coordinates
vm_vec_sub(&tempv, &l->vec2, &Light_base );
vm_vec_rotate(&l->local_vec2, &tempv, &Light_matrix );
}
break;
case Light_Type::Cone:
break;
default:
Int3(); // Invalid light type
}
}
void light_add_directional(const vec3d* dir, int sun_index, bool no_glare, const hdr_color* new_color, const float source_radius)
{
Assert(new_color!= nullptr);
light_add_directional(dir, sun_index, no_glare, new_color->i(), new_color->r(), new_color->g(), new_color->b(), source_radius);
}
namespace ltp=lighting_profiles;
void light_add_directional(const vec3d *dir, int sun_index, bool no_glare, float intensity, float r, float g, float b, const float source_radius)
{
if (Lighting_off) return;
Num_lights++;
light l{};
l.type = Light_Type::Directional;
l.flags = no_glare ? (LF_DEFAULT | LF_NO_GLARE) : LF_DEFAULT;
l.sun_index = sun_index;
vm_vec_copy_scale( &l.vec, dir, -1.0f );
l.r = r;
l.g = g;
l.b = b;
//configurable global tuning of light qualities
auto lp = ltp::current();
l.intensity = lp->overall_brightness.handle(lp->directional_light_brightness.handle(intensity));
l.rada = 0.0f;
l.radb = 0.0f;
l.rada_squared = l.rada*l.rada;
l.radb_squared = l.radb*l.radb;
l.instance = Num_lights-1;
l.source_radius = MAX(0.0f,source_radius);
Lights.push_back(l);
Static_light.push_back(l);
}
void light_add_point(const vec3d* pos, float r1, float r2, const hdr_color* new_color, float source_radius, bool no_rt_shadow)
{
Assert(new_color!= nullptr);
light_add_point(pos, r1, r2, new_color->i(), new_color->r(), new_color->g(), new_color->b(), source_radius, no_rt_shadow);
}
void light_add_point(const vec3d *pos, float r1, float r2, float intensity, float r, float g, float b, const float source_radius, bool no_rt_shadow)
{
Assertion( r1 > 0.0f, "Invalid radius r1 specified for light: %f. Radius must be > 0.0f. Examine stack trace to determine culprit.\n", r1 );
Assertion( r2 > 0.0f, "Invalid radius r2 specified for light: %f. Radius must be > 0.0f. Examine stack trace to determine culprit.\n", r2 );
if (Lighting_off) return;
if (!Lighting_flag) return;
light l{};
Num_lights++;
l.type = Light_Type::Point;
l.flags = no_rt_shadow ? (LF_DEFAULT | LF_NO_RT_SHADOW) : LF_DEFAULT;
l.sun_index = -1;
l.vec = *pos;
l.r = r;
l.g = g;
l.b = b;
//configurable global tuning of light qualities
auto lp = ltp::current();
l.intensity = lp->overall_brightness.handle(lp->point_light_brightness.handle(intensity));
l.rada = lp->point_light_radius.handle(r1);
l.radb = lp->point_light_radius.handle(r2);
l.rada_squared = l.rada*l.rada;
l.radb_squared = l.radb*l.radb;
l.instance = Num_lights-1;
l.source_radius = MAX(0.0f,source_radius);
Lights.push_back(l);
}
void light_add_tube(const vec3d* p0, const vec3d* p1, float r1, float r2, const hdr_color* new_color, const float source_radius)
{
Assert(new_color!= nullptr);
light_add_tube(p0, p1, r1, r2, new_color->i(), new_color->r(), new_color->g(), new_color->b(), source_radius);
}
void light_add_tube(const vec3d *p0, const vec3d *p1, float r1, float r2, float intensity, float r, float g, float b, const float source_radius)
{
Assertion(r1 > 0.0f, "Invalid radius r1 specified for light: %f. Radius must be > 0.0f. Examine stack trace to determine culprit.\n", r1);
Assertion(r2 > 0.0f, "Invalid radius r2 specified for light: %f. Radius must be > 0.0f. Examine stack trace to determine culprit.\n", r2);
if (Lighting_off) return;
if (!Lighting_flag) return;
light l{};
Num_lights++;
l.type = Light_Type::Tube;
l.flags = LF_DEFAULT;
l.sun_index = -1;
l.vec = *p0;
l.vec2 = *p1;
l.r = r;
l.g = g;
l.b = b;
//configurable global tuning of light qualities
auto lp = ltp::current();
l.intensity = lp->overall_brightness.handle(lp->tube_light_brightness.handle(intensity));
l.rada = lp->tube_light_radius.handle(r1);
l.radb = lp->tube_light_radius.handle(r2);
l.rada_squared = l.rada*l.rada;
l.radb_squared = l.radb*l.radb;
l.instance = Num_lights-1;
l.source_radius = MAX(0.0f,source_radius);
Lights.push_back(l);
}
void light_rotate_all()
{
if ( Lighting_off ) return;
for (auto& l : Lights)
light_rotate(&l);
// the sun lights are separate copies, and light_apply_rgb reads these
for (auto& l : Static_light)
light_rotate(&l);
}
/**
* Return the # of global light sources
*/
int light_get_global_count()
{
return static_cast<int>(Static_light.size());
}
/**
* Fills direction of global light source N in pos.
*
* @param pos Position
* @param n Light source
*
* Returns false if there is no global light.
*/
bool light_get_global_dir(vec3d *pos, int n)
{
if (!SCP_vector_inbounds(Static_light, n)) {
return false;
}
if (pos) {
*pos = Static_light[n].vec;
vm_vec_scale( pos, -1.0f );
}
return true;
}
bool light_has_glare(int n)
{
if (!SCP_vector_inbounds(Static_light, n)) {
return false;
}
return (Static_light[n].flags & LF_NO_GLARE) == 0;
}
int light_get_sun_index(int n)
{
if (!SCP_vector_inbounds(Static_light, n)) {
return -1;
}
return Static_light[n].sun_index;
}
int light_find_for_sun(int sun_index)
{
for (size_t n = 0; n < Static_light.size(); ++n)
if (Static_light[n].sun_index == sun_index)
return static_cast<int>(n);
return -1;
}
void light_apply_rgb( ubyte *param_r, ubyte *param_g, ubyte *param_b, const vec3d *pos, const vec3d *norm, float static_light_level )
{
int idx;
float rval, gval, bval;
if ( Lighting_off ) {
*param_r = 255;
*param_g = 255;
*param_b = 255;
return;
}
// Factor in ambient light
rval = Ambient_light;
gval = Ambient_light;
bval = Ambient_light;
// Factor in light from sun if there is one
if ( !Light_in_shadow ){
// apply all sun lights
for (idx = 0; idx < (int)Static_light.size(); idx++) {
float ltmp;
// calculate light from surface normal
ltmp = -vm_vec_dot(&Static_light[idx].local_vec, norm )*Static_light[idx].intensity*Reflective_light; // reflective light
if ( ltmp > 0.0f ) {
rval += Static_light[idx].r * ltmp;
gval += Static_light[idx].g * ltmp;
bval += Static_light[idx].b * ltmp;
}
}
}
// At this point, l must be between 0 and 0.75 (0.75-1.0 is for dynamic light only)
CLAMP(rval, 0.0f, 0.75f);
CLAMP(gval, 0.0f, 0.75f);
CLAMP(bval, 0.0f, 0.75f);
rval *= static_light_level;
gval *= static_light_level;
bval *= static_light_level;
vec3d to_light;
float dot, dist;
vec3d temp;
for (const auto& l : Lights) {
dist = -1.0f;
switch(l.type){
// point lights
case Light_Type::Point:
vm_vec_sub( &to_light, &l.local_vec, pos );
break;
// tube lights
case Light_Type::Tube:
if(vm_vec_dist_to_line(pos, &l.local_vec, &l.local_vec2, &temp, &dist) != 0){
continue;
}
vm_vec_sub(&to_light, &temp, pos);
dist *= dist; // since we use radius squared
break;
case Light_Type::Directional:
continue;
case Light_Type::Cone:
continue;
// others. BAD
default:
Error("Unknown light type in light_apply_rgb!\n");
continue;
}
dot = vm_vec_dot(&to_light, norm);
// dot = 1.0f;
if ( dot > 0.0f ) {
// indicating that we already calculated the distance (vm_vec_dist_to_line(...) does this for us)
if(dist < 0.0f){
dist = vm_vec_mag_squared(&to_light);
}
if ( dist < l.rada_squared ) {
float ratio;
ratio = l.intensity*dot;
ratio *= 0.25f;
rval += l.r*ratio;
gval += l.g*ratio;
bval += l.b*ratio;
} else if ( dist < l.radb_squared ) {
float ratio;
// dist from 0 to
float nnum = dist - l.rada_squared;
float dden = l.radb_squared - l.rada_squared;
ratio = (1.0f - nnum / dden)*dot*l.intensity;
ratio *= 0.25f;
rval += l.r*ratio;
gval += l.g*ratio;
bval += l.b*ratio;
}
}
}
float m = rval;
if ( gval > m ) m = gval;
if ( bval > m ) m = bval;
if ( m > 1.0f ) {
float im = 1.0f / m;
rval *= im;
gval *= im;
bval *= im;
}
CLAMP(rval, 0.0f, 1.0f);
CLAMP(gval, 0.0f, 1.0f);
CLAMP(bval, 0.0f, 1.0f);
*param_r = ubyte(fl2i(rval*255.0f));
*param_g = ubyte(fl2i(gval*255.0f));
*param_b = ubyte(fl2i(bval*255.0f));
}
void light_add_cone(const vec3d *pos, const vec3d *dir, float angle, float inner_angle, bool dual_cone, float r1, float r2, const hdr_color* new_color, const float source_radius)
{
Assert(new_color!= nullptr);
light_add_cone(pos, dir, angle, inner_angle, dual_cone, r1, r2, new_color->i(), new_color->r(), new_color->g(), new_color->b(), source_radius);
}
void light_add_cone(const vec3d *pos, const vec3d *dir, float angle, float inner_angle, bool dual_cone, float r1, float r2, float intensity, float r, float g, float b, const float source_radius )
{
Assertion( r1 > 0.0f, "Invalid radius r1 specified for light: %f. Radius must be > 0.0f. Examine stack trace to determine culprit.\n", r1 );
Assertion( r2 > 0.0f, "Invalid radius r2 specified for light: %f. Radius must be > 0.0f. Examine stack trace to determine culprit.\n", r2 );
if ( Lighting_off ) return;
if (!Lighting_flag) return;
light l{};
Num_lights++;
l.type = Light_Type::Cone;
l.flags = dual_cone ? (LF_DEFAULT | LF_DUAL_CONE) : LF_DEFAULT;
l.sun_index = -1;
l.vec = *pos;
l.vec2= *dir;
l.cone_angle = angle;
l.cone_inner_angle = inner_angle;
l.r = r;
l.g = g;
l.b = b;
auto lp = ltp::current();
l.intensity = lp->overall_brightness.handle(lp->cone_light_brightness.handle(intensity));
l.rada = lp->cone_light_radius.handle(r1);
l.radb = lp->cone_light_radius.handle(r2);
l.rada_squared = l.rada*l.rada;
l.radb_squared = l.radb*l.radb;
l.instance = Num_lights-1;
l.source_radius = MAX(0.0f,source_radius);
Lights.push_back(l);
}
bool light_compare_by_type(const light &a, const light &b)
{
return a.type < b.type;
}
void scene_lights::addLight(const light *light_ptr)
{
Assert(light_ptr != NULL);
AllLights.push_back(*light_ptr);
if ( light_ptr->type == Light_Type::Directional ) {
StaticLightIndices.push_back(AllLights.size() - 1);
}
}
void scene_lights::setLightFilter(const vec3d *pos, float rad)
{
size_t i = 0;
// clear out current filtered lights
FilteredLights.clear();
for ( auto& l : AllLights ) {
switch ( l.type ) {
case Light_Type::Directional:
++i;
continue;
case Light_Type::Point: {
vec3d to_light;
float dist_squared, max_dist_squared;
vm_vec_sub( &to_light, &l.vec, pos );
dist_squared = vm_vec_mag_squared(&to_light);
max_dist_squared = l.radb+rad;
max_dist_squared *= max_dist_squared;
if ( dist_squared < max_dist_squared ) {
FilteredLights.push_back(i);
}
}
break;
case Light_Type::Tube: {
vec3d nearest;
float dist_squared, max_dist_squared;
vm_vec_dist_squared_to_line(pos,&l.vec,&l.vec2,&nearest,&dist_squared);
max_dist_squared = l.radb+rad;
max_dist_squared *= max_dist_squared;
if ( dist_squared < max_dist_squared ) {
FilteredLights.push_back(i);
}
}
break;
case Light_Type::Cone:
break;
default:
break;
}
++i;
}
}
light_indexing_info scene_lights::bufferLights()
{
size_t i;
light_indexing_info light_info;
light_info.index_start = 0;
light_info.num_lights = 0;
// make sure that there are lights to bind?
if ( FilteredLights.empty() ) {
return light_info;
}
light_info.index_start = BufferedLights.size();
for ( i = 0; i < FilteredLights.size(); ++i ) {
BufferedLights.push_back(FilteredLights[i]);
}
light_info.num_lights = FilteredLights.size();
return light_info;
}
void scene_lights::resetLightState()
{
current_light_index = static_cast<size_t>(-1);
current_num_lights = static_cast<size_t>(-1);
}
bool scene_lights::setLights(const light_indexing_info *info)
{
if ( info->index_start == current_light_index && info->num_lights == current_num_lights ) {
// don't need to set new lights since the ones requested to be set are currently set
return false;
}
current_light_index = info->index_start;
current_num_lights = info->num_lights;
gr_reset_lighting();
for ( size_t i = 0; i < StaticLightIndices.size(); ++i) {
auto light_index = StaticLightIndices[i];
gr_set_light( &AllLights[light_index] );
}
extern bool Deferred_lighting;
if ( Deferred_lighting ) {
gr_set_lighting();
return false;
}
auto index_start = info->index_start;
auto num_lights = info->num_lights;
// check if there are any lights to actually set
if ( num_lights <= 0 ) {
gr_set_lighting();
return false;
}
// we definitely shouldn't be exceeding the number of buffered lights
Assert(index_start + num_lights <= BufferedLights.size());
for ( size_t i = 0; i < num_lights; ++i ) {
auto buffered_light_index = index_start + i;
auto light_index = BufferedLights[buffered_light_index];
gr_set_light(&AllLights[light_index]);
}
gr_set_lighting();
return true;
}