quakeforge/libs/client/cl_light.c
Bill Currie 35eec0b2e5 [ecs] Implement hierarchies as components
The main goal of this change was to make it easier to tell when a
hierarchy has been deleted, but as a side benefit, it got rid of the use
of PR_RESMAP. Also, it's easy to track the number of hierarchies.

Unfortunately, it showed how brittle the component side of the ECS is
(scene and canvas registries assumed their components were the first (no
long the case), thus the sweeping changes).

Centerprint doesn't work (but it hasn't for a while).
2024-01-02 16:38:01 +09:00

340 lines
8.1 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->base + scene_light];
auto lefrags = &scene->reg->comp_pools[scene->base + 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);
}