mirror of
https://git.code.sf.net/p/quake/quakeforge
synced 2025-02-24 20:51:35 +00:00
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).
340 lines
8.1 KiB
C
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);
|
|
}
|