mirror of
https://git.code.sf.net/p/quake/quakeforge
synced 2024-12-02 00:52:19 +00:00
72f6048a20
This eliminates the O(N^2) (N = map leaf count) operation of finding visible lights and will later allow for finer culling of the lights as they can be tested against the leaf volume (which they currently are not as this was just getting things going). However, this has severely hurt ad_tears' performance (I suspect due to the extreme number of leafs), but the speed seems to be very steady. Hopefully, reconstructing the vis clusters will help (I imagine it will help in many places, not just lights).
340 lines
8 KiB
C
340 lines
8 KiB
C
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
|
|
#include <string.h>
|
|
|
|
#include "QF/dstring.h"
|
|
#include "QF/mathlib.h"
|
|
#include "QF/model.h"
|
|
#include "QF/plist.h"
|
|
#include "QF/progs.h" //for ED_ConvertToPlist
|
|
#include "QF/set.h"
|
|
#include "QF/ecs.h"
|
|
#include "QF/scene/entity.h"
|
|
#include "QF/scene/light.h"
|
|
#include "QF/scene/scene.h"
|
|
#include "QF/simd/vec4f.h"
|
|
|
|
#include "client/world.h"
|
|
|
|
static void
|
|
dump_light (light_t *light, efrag_t *efrags)
|
|
{
|
|
int leafcount = 0;
|
|
for (auto e = efrags; e; e = e->leafnext) {
|
|
leafcount++;
|
|
}
|
|
Sys_MaskPrintf (SYS_lighting,
|
|
"[%g, %g, %g] %g, "
|
|
"[%g, %g, %g, %g], [%g %g %g] %g, [%g, %g, %g, %g] %d\n",
|
|
VEC4_EXP (light->color),
|
|
VEC4_EXP (light->position),
|
|
VEC4_EXP (light->direction),
|
|
VEC4_EXP (light->attenuation),
|
|
leafcount);
|
|
}
|
|
|
|
static float
|
|
parse_float (const char *str, float defval)
|
|
{
|
|
float val = defval;
|
|
if (str) {
|
|
char *end;
|
|
val = strtof (str, &end);
|
|
if (end == str) {
|
|
val = defval;
|
|
}
|
|
}
|
|
return val;
|
|
}
|
|
|
|
static void
|
|
parse_vector (const char *str, vec_t *val)
|
|
{
|
|
if (str) {
|
|
int num = sscanf (str, "%f %f %f", VectorExpandAddr (val));
|
|
while (num < 3) {
|
|
val[num++] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
static float
|
|
ecos (float ang)
|
|
{
|
|
if (ang == 90 || ang == -90) {
|
|
return 0;
|
|
}
|
|
if (ang == 180 || ang == -180) {
|
|
return -1;
|
|
}
|
|
if (ang == 0 || ang == 360) {
|
|
return 1;
|
|
}
|
|
return cos (ang * M_PI / 180);
|
|
}
|
|
|
|
static float
|
|
esin (float ang)
|
|
{
|
|
if (ang == 90) {
|
|
return 1;
|
|
}
|
|
if (ang == -90) {
|
|
return -1;
|
|
}
|
|
if (ang == 180 || ang == -180) {
|
|
return 0;
|
|
}
|
|
if (ang == 0 || ang == 360) {
|
|
return 0;
|
|
}
|
|
return sin (ang * M_PI / 180);
|
|
}
|
|
|
|
static vec4f_t
|
|
sun_vector (const vec_t *ang)
|
|
{
|
|
// ang is yaw, pitch (maybe roll, but ignored
|
|
// negative as the vector points *to* the sun, but ang specifies the
|
|
// direction from the sun
|
|
vec4f_t vec = {
|
|
-ecos (ang[1]) * ecos (ang[0]),
|
|
-ecos (ang[1]) * esin (ang[0]),
|
|
-esin (ang[1]),
|
|
0,
|
|
};
|
|
return vec;
|
|
}
|
|
|
|
static void
|
|
parse_sun (lightingdata_t *ldata, plitem_t *entity)
|
|
{
|
|
light_t light = {};
|
|
float sunlight;
|
|
//float sunlight2;
|
|
vec3_t sunangle = { 0, -90, 0 };
|
|
|
|
Light_EnableSun (ldata);
|
|
sunlight = parse_float (PL_String (PL_ObjectForKey (entity,
|
|
"_sunlight")), 0);
|
|
//sunlight2 = parse_float (PL_String (PL_ObjectForKey (entity,
|
|
// "_sunlight2")), 0);
|
|
parse_vector (PL_String (PL_ObjectForKey (entity, "_sun_mangle")),
|
|
sunangle);
|
|
if (sunlight <= 0) {
|
|
return;
|
|
}
|
|
VectorSet (1, 1, 1, light.color);
|
|
light.color[3] = sunlight;
|
|
light.position = sun_vector (sunangle);
|
|
light.direction = light.position;
|
|
light.direction[3] = 1;
|
|
light.attenuation = (vec4f_t) { 0, 0, 1, 0 };
|
|
Light_AddLight (ldata, &light, 0);
|
|
}
|
|
|
|
static vec4f_t
|
|
parse_position (const char *str)
|
|
{
|
|
vec3_t vec = {};
|
|
sscanf (str, "%f %f %f", VectorExpandAddr (vec));
|
|
return (vec4f_t) {vec[0], vec[1], vec[2], 1};
|
|
}
|
|
|
|
static void
|
|
parse_light (light_t *light, int *style, const plitem_t *entity,
|
|
const plitem_t *targets)
|
|
{
|
|
const char *str;
|
|
int model = 0;
|
|
float atten = 1;
|
|
|
|
/*Sys_Printf ("{\n");
|
|
for (int i = PL_D_NumKeys (entity); i-- > 0; ) {
|
|
const char *field = PL_KeyAtIndex (entity, i);
|
|
const char *value = PL_String (PL_ObjectForKey (entity, field));
|
|
Sys_Printf ("\t%s = %s\n", field, value);
|
|
}
|
|
Sys_Printf ("}\n");*/
|
|
|
|
// omnidirectional light (unit length xyz so not treated as ambient)
|
|
light->direction = (vec4f_t) { 0, 0, 1, 1 };
|
|
// bright white
|
|
light->color = (vec4f_t) { 1, 1, 1, 300 };
|
|
|
|
if ((str = PL_String (PL_ObjectForKey (entity, "origin")))) {
|
|
light->position = parse_position (str);
|
|
}
|
|
|
|
if ((str = PL_String (PL_ObjectForKey (entity, "target")))) {
|
|
plitem_t *target = PL_ObjectForKey (targets, str);
|
|
vec4f_t dir = { 1, 0, 0, 0 };
|
|
if (target) {
|
|
if ((str = PL_String (PL_ObjectForKey (target, "origin")))) {
|
|
dir = parse_position (str);
|
|
dir = normalf (dir - light->position);
|
|
}
|
|
}
|
|
|
|
float angle = 40;
|
|
if ((str = PL_String (PL_ObjectForKey (entity, "angle")))) {
|
|
angle = atof (str);
|
|
}
|
|
dir[3] = -cos (angle * M_PI / 360); // half angle
|
|
light->direction = dir;
|
|
}
|
|
|
|
if ((str = PL_String (PL_ObjectForKey (entity, "light_lev")))
|
|
|| (str = PL_String (PL_ObjectForKey (entity, "_light")))) {
|
|
light->color[3] = atof (str);
|
|
}
|
|
|
|
if ((str = PL_String (PL_ObjectForKey (entity, "style")))) {
|
|
*style = atoi (str) & 0x3f;
|
|
}
|
|
|
|
if ((str = PL_String (PL_ObjectForKey (entity, "delay")))) {
|
|
model = atoi (str) & 0x7;
|
|
if (model == LM_INVERSE2) {
|
|
model = LM_INVERSE3; //FIXME for marcher (need a map)
|
|
}
|
|
}
|
|
|
|
if ((str = PL_String (PL_ObjectForKey (entity, "color")))
|
|
|| (str = PL_String (PL_ObjectForKey (entity, "_color")))) {
|
|
union {
|
|
float a[4];
|
|
vec4f_t v;
|
|
} color = { .v = light->color };
|
|
sscanf (str, "%f %f %f", VectorExpandAddr (color.a));
|
|
light->color = color.v;
|
|
if (light->color[0] > 1 || light->color[1] > 1 || light->color[2] > 1) {
|
|
VectorScale (light->color, 1/255.0, light->color);
|
|
}
|
|
}
|
|
|
|
if ((str = PL_String (PL_ObjectForKey (entity, "wait")))) {
|
|
atten = atof (str);
|
|
if (atten <= 0) {
|
|
atten = 1;
|
|
}
|
|
}
|
|
|
|
// The light's intensity is calculated as
|
|
// I = (1 - a.w * r.y) / dot (a, r)
|
|
// where a is attenuation and r = vec4 (d*d, d, 1, 0)
|
|
// thus giving linear falloff for a = vec4 (0, 0, 1, 1/maxdist)
|
|
// and 1/(A*d*d + B*d + C) for a = vec4 (A, B, C, 0)
|
|
// Other factors contribute to the final intensity (cone angle etc)
|
|
vec4f_t attenuation = { // inverse square
|
|
1, 0, 0,
|
|
0,
|
|
};
|
|
switch (model) {
|
|
case LM_LINEAR:
|
|
attenuation = (vec4f_t) {
|
|
0, 0, 1,
|
|
atten / fabsf (light->color[3]),
|
|
};
|
|
break;
|
|
case LM_INVERSE:
|
|
attenuation = (vec4f_t) {
|
|
0, atten / 128, 0,
|
|
0,
|
|
};
|
|
break;
|
|
case LM_INVERSE2:
|
|
attenuation = (vec4f_t) {
|
|
atten * atten / 16384, 0, 0,
|
|
0,
|
|
};
|
|
break;
|
|
case LM_INFINITE:
|
|
attenuation = (vec4f_t) {
|
|
0, 0, 1,
|
|
0,
|
|
};
|
|
break;
|
|
case LM_AMBIENT:
|
|
attenuation = (vec4f_t) {
|
|
0, 0, 1,
|
|
0,
|
|
};
|
|
light->direction = (vec4f_t) { 0, 0, 0, 1 };
|
|
break;
|
|
case LM_INVERSE3:
|
|
attenuation = (vec4f_t) {
|
|
atten * atten / 16384, 2 * atten / 128, 1,
|
|
0,
|
|
};
|
|
break;
|
|
}
|
|
light->attenuation = attenuation;
|
|
}
|
|
|
|
void
|
|
CL_LoadLights (plitem_t *entities, scene_t *scene)
|
|
{
|
|
lightingdata_t *ldata = scene->lights;
|
|
model_t *model = scene->worldmodel;
|
|
|
|
Light_ClearLights (ldata);
|
|
ldata->sun_pvs = set_new_size (model->brush.visleafs);
|
|
if (!entities) {
|
|
return;
|
|
}
|
|
|
|
plitem_t *targets = PL_NewDictionary (0);
|
|
|
|
// find all the targets so spotlights can be aimed
|
|
for (int i = 1; i < PL_A_NumObjects (entities); i++) {
|
|
plitem_t *entity = PL_ObjectAtIndex (entities, i);
|
|
const char *targetname = PL_String (PL_ObjectForKey (entity,
|
|
"targetname"));
|
|
if (targetname && !PL_ObjectForKey (targets, targetname)) {
|
|
PL_D_AddObject (targets, targetname, entity);
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < PL_A_NumObjects (entities); i++) {
|
|
plitem_t *entity = PL_ObjectAtIndex (entities, i);
|
|
const char *classname = PL_String (PL_ObjectForKey (entity,
|
|
"classname"));
|
|
if (!classname) {
|
|
continue;
|
|
}
|
|
if (!strcmp (classname, "worldspawn")) {
|
|
// parse_sun can add many lights
|
|
parse_sun (ldata, entity);
|
|
const char *str;
|
|
if ((str = PL_String (PL_ObjectForKey (entity, "light_lev")))) {
|
|
light_t light = {};
|
|
light.color = (vec4f_t) { 1, 1, 1, atof (str) };
|
|
light.attenuation = (vec4f_t) { 0, 0, 1, 0 };
|
|
light.direction = (vec4f_t) { 0, 0, 0, 1 };
|
|
Light_AddLight (ldata, &light, 0);
|
|
}
|
|
} else if (!strncmp (classname, "light", 5)) {
|
|
light_t light = {};
|
|
int style = 0;
|
|
|
|
parse_light (&light, &style, entity, targets);
|
|
// some lights have 0 output, so drop them
|
|
if (light.color[3]) {
|
|
Light_AddLight (ldata, &light, style);
|
|
}
|
|
}
|
|
}
|
|
PL_Release (targets);
|
|
|
|
auto lights = &scene->reg->comp_pools[scene_light];
|
|
auto lefrags = &scene->reg->comp_pools[scene_efrags];
|
|
for (uint32_t i = 0; i < lights->count; i++) {
|
|
auto light = &((light_t *)lights->data)[i];
|
|
auto efrags = ((efrag_t **)lefrags->data)[i];
|
|
dump_light (light, efrags);
|
|
}
|
|
Sys_MaskPrintf (SYS_lighting, "loaded %d lights\n", lights->count);
|
|
}
|