light: add _switchableshadow_target

Switchable shadows setup which works in id1

Fixes #432
This commit is contained in:
Eric Wasylishen 2024-07-20 23:11:26 -06:00
parent 8ac82b7c79
commit e5f01ce0e3
8 changed files with 275 additions and 7 deletions

View File

@ -1099,10 +1099,10 @@ static int StyleOffset(int style, const mface_t *face, const faceextents_t &face
} }
/** /**
* Samples the lightmap at an integer coordinate in style 0 * Samples the lightmap at an integer coordinate in the given style
*/ */
qvec3b LM_Sample(const mbsp_t *bsp, const mface_t *face, const lit_variant_t *lit, const faceextents_t &faceextents, qvec3b LM_Sample(const mbsp_t *bsp, const mface_t *face, const lit_variant_t *lit, const faceextents_t &faceextents,
int byte_offset_of_face, qvec2i coord) int byte_offset_of_face, qvec2i coord, int style)
{ {
if (byte_offset_of_face == -1) { if (byte_offset_of_face == -1) {
return {0, 0, 0}; return {0, 0, 0};
@ -1113,7 +1113,7 @@ qvec3b LM_Sample(const mbsp_t *bsp, const mface_t *face, const lit_variant_t *li
Q_assert(coord[0] < faceextents.width()); Q_assert(coord[0] < faceextents.width());
Q_assert(coord[1] < faceextents.height()); Q_assert(coord[1] < faceextents.height());
int style_offset = StyleOffset(0, face, faceextents); int style_offset = StyleOffset(style, face, faceextents);
if (style_offset == -1) { if (style_offset == -1) {
return {0, 0, 0}; return {0, 0, 0};
} }

View File

@ -665,6 +665,11 @@ If used on func_detail* or func_group, a full qbsp pass need to be run.
through the switchable shadow casters, regardless of whether the shadow through the switchable shadow casters, regardless of whether the shadow
is off or on. is off or on.
.. seealso::
The light entity key :light-key:`_switchableshadow_target` allows using switchable
shadows in ID1 Quake, without custom QC, although the setup is more awkward.
.. bmodel-key:: "_dirt" "n" .. bmodel-key:: "_dirt" "n"
For brush models, -1 prevents dirtmapping on the brush model. Useful For brush models, -1 prevents dirtmapping on the brush model. Useful
@ -918,6 +923,21 @@ Point Lights
Set to 1 to make the light compiler ignore this entity (prevents it Set to 1 to make the light compiler ignore this entity (prevents it
from casting any light). e.g. could be useful with rtlights. from casting any light). e.g. could be useful with rtlights.
.. light-key:: "_switchableshadow_target" "name"
Calculate lighting with and without bmodels with a "targetname" equal to "name",
and stores the resulting switchable shadow data in a light style which is stored in this light
entity's "style" key.
You should give this light a :light-key:`targetname` and typically set "spawnflags" "1" (start off).
Implies :light-key:`_nostaticlight` (this entity itself does not cast any light).
.. hint::
If your mod supports it, you should prefer to use bmodel key :bmodel-key:`_switchableshadow`
to enable switchable shadows.
Spotlights Spotlights
---------- ----------

View File

@ -176,7 +176,7 @@ public:
}; };
qvec3b LM_Sample(const mbsp_t *bsp, const mface_t *face, const lit_variant_t *lit, const faceextents_t &faceextents, qvec3b LM_Sample(const mbsp_t *bsp, const mface_t *face, const lit_variant_t *lit, const faceextents_t &faceextents,
int byte_offset_of_face, qvec2i coord); int byte_offset_of_face, qvec2i coord, int style = 0);
qvec3f LM_Sample_HDR(const mbsp_t *bsp, qvec3f LM_Sample_HDR(const mbsp_t *bsp,
const mface_t *face, const mface_t *face,

View File

@ -113,6 +113,7 @@ public:
settings::setting_int32 light_channel_mask; settings::setting_int32 light_channel_mask;
settings::setting_int32 shadow_channel_mask; settings::setting_int32 shadow_channel_mask;
settings::setting_bool nonudge; settings::setting_bool nonudge;
settings::setting_string switchableshadow_target;
light_t(); light_t();
@ -143,6 +144,10 @@ std::vector<std::unique_ptr<light_t>> &GetLights();
const std::vector<entdict_t> &GetEntdicts(); const std::vector<entdict_t> &GetEntdicts();
std::vector<sun_t> &GetSuns(); std::vector<sun_t> &GetSuns();
std::vector<entdict_t> &GetRadLights(); std::vector<entdict_t> &GetRadLights();
/**
* Returns the light entity that has "_switchableshadow_target" set to the given value, or nullptr.
*/
light_t *LightWithSwitchableShadowTargetValue(const std::string &target);
const std::vector<std::unique_ptr<light_t>> &GetSurfaceLightTemplates(); const std::vector<std::unique_ptr<light_t>> &GetSurfaceLightTemplates();

View File

@ -42,6 +42,7 @@ static std::vector<std::pair<std::string, int>> lightstyleForTargetname;
static std::vector<std::unique_ptr<light_t>> surfacelight_templates; static std::vector<std::unique_ptr<light_t>> surfacelight_templates;
static std::ofstream surflights_dump_file; static std::ofstream surflights_dump_file;
static fs::path surflights_dump_filename; static fs::path surflights_dump_filename;
static std::map<std::string, light_t*> lights_by_switchableshadow_target;
/** /**
* Resets global data in this file * Resets global data in this file
@ -58,6 +59,7 @@ void ResetLightEntities()
surfacelight_templates.clear(); surfacelight_templates.clear();
surflights_dump_file = {}; surflights_dump_file = {};
surflights_dump_filename.clear(); surflights_dump_filename.clear();
lights_by_switchableshadow_target.clear();
} }
std::vector<std::unique_ptr<light_t>> &GetLights() std::vector<std::unique_ptr<light_t>> &GetLights()
@ -80,6 +82,16 @@ std::vector<entdict_t> &GetRadLights()
return radlights; return radlights;
} }
light_t *LightWithSwitchableShadowTargetValue(const std::string &target)
{
auto it = lights_by_switchableshadow_target.find(target);
if (it == lights_by_switchableshadow_target.end())
return nullptr;
return it->second;
}
/* surface lights */ /* surface lights */
static void MakeSurfaceLights(const mbsp_t *bsp); static void MakeSurfaceLights(const mbsp_t *bsp);
@ -121,7 +133,8 @@ light_t::light_t()
surflight_atten{this, "surflight_atten", 1.f}, surflight_atten{this, "surflight_atten", 1.f},
light_channel_mask{this, "light_channel_mask", CHANNEL_MASK_DEFAULT}, light_channel_mask{this, "light_channel_mask", CHANNEL_MASK_DEFAULT},
shadow_channel_mask{this, "shadow_channel_mask", CHANNEL_MASK_DEFAULT}, shadow_channel_mask{this, "shadow_channel_mask", CHANNEL_MASK_DEFAULT},
nonudge{this, "nonudge", false} nonudge{this, "nonudge", false},
switchableshadow_target{this, "switchableshadow_target", ""}
{ {
} }
@ -1062,6 +1075,14 @@ void LoadEntities(const settings::worldspawn_keys &cfg, const mbsp_t *bsp)
entity->projfov.value(), entity->projectionmatrix); entity->projfov.value(), entity->projectionmatrix);
} }
// vanilla-compatible switchable shadows
const std::string &switchableshadow_target = entity->switchableshadow_target.value();
if (!switchableshadow_target.empty()) {
entity->nostaticlight.set_value(true, settings::source::DEFAULT);
lights_by_switchableshadow_target[switchableshadow_target] = entity.get();
}
CheckEntityFields(bsp, cfg, entity.get()); CheckEntityFields(bsp, cfg, entity.get());
} }
} }

View File

@ -725,6 +725,13 @@ static void FindModelInfo(const mbsp_t *bsp)
// apply settings // apply settings
info->set_settings(*entdict, settings::source::MAP); info->set_settings(*entdict, settings::source::MAP);
// vanilla-compatible switchable shadows
if (auto *light = LightWithSwitchableShadowTargetValue(entdict->get("targetname"))) {
// take the "style" key from this light entity and enable switchable shadows on ourself
info->switchableshadow.set_value(true, settings::source::DEFAULT);
info->switchshadstyle.set_value(light->style.value(), settings::source::DEFAULT);
}
/* Check if this model will cast shadows (shadow => shadowself) */ /* Check if this model will cast shadows (shadow => shadowself) */
if (info->switchableshadow.boolValue()) { if (info->switchableshadow.boolValue()) {
Q_assert(info->switchshadstyle.value() != 0); Q_assert(info->switchshadstyle.value() != 0);

View File

@ -0,0 +1,184 @@
// Game: Quake
// Format: Standard
// entity 0
{
"classname" "worldspawn"
"_tb_textures" "textures/e1u1"
"wad" "deprecated/free_wad.wad"
// brush 0
{
( 480 1088 928 ) ( 480 1089 928 ) ( 480 1088 929 ) bolt14 0 32 0 1 1
( 704 1088 928 ) ( 704 1088 929 ) ( 705 1088 928 ) bolt14 0 32 0 1 1
( 704 1088 928 ) ( 705 1088 928 ) ( 704 1089 928 ) bolt14 0 0 0 1 1
( 944 1472 944 ) ( 944 1473 944 ) ( 945 1472 944 ) bolt14 0 0 0 1 1
( 944 1488 944 ) ( 945 1488 944 ) ( 944 1488 945 ) bolt14 0 32 0 1 1
( 1056 1472 944 ) ( 1056 1472 945 ) ( 1056 1473 944 ) bolt14 0 32 0 1 1
}
// brush 1
{
( 480 1088 1248 ) ( 480 1089 1248 ) ( 480 1088 1249 ) bolt14 0 96 0 1 1
( 704 1072 1248 ) ( 704 1072 1249 ) ( 705 1072 1248 ) bolt14 0 96 0 1 1
( 704 1088 1248 ) ( 705 1088 1248 ) ( 704 1089 1248 ) bolt14 0 0 0 1 1
( 944 1472 1264 ) ( 944 1473 1264 ) ( 945 1472 1264 ) bolt14 0 0 0 1 1
( 944 1488 1264 ) ( 945 1488 1264 ) ( 944 1488 1265 ) bolt14 0 96 0 1 1
( 1056 1472 1264 ) ( 1056 1472 1265 ) ( 1056 1473 1264 ) bolt14 0 96 0 1 1
}
// brush 2
{
( 480 1072 928 ) ( 480 1073 928 ) ( 480 1072 929 ) bolt14 16 32 0 1 1
( 704 1072 928 ) ( 704 1072 929 ) ( 705 1072 928 ) bolt14 0 32 0 1 1
( 704 1072 928 ) ( 705 1072 928 ) ( 704 1073 928 ) bolt14 0 -16 0 1 1
( 944 1456 1248 ) ( 944 1457 1248 ) ( 945 1456 1248 ) bolt14 0 -16 0 1 1
( 944 1088 944 ) ( 945 1088 944 ) ( 944 1088 945 ) bolt14 0 32 0 1 1
( 1056 1456 944 ) ( 1056 1456 945 ) ( 1056 1457 944 ) bolt14 16 32 0 1 1
}
// brush 3
{
( 480 1392 928 ) ( 480 1393 928 ) ( 480 1392 929 ) bolt14 -48 32 0 1 1
( 832 1488 928 ) ( 832 1488 929 ) ( 833 1488 928 ) bolt14 -128 32 0 1 1
( 832 1392 928 ) ( 833 1392 928 ) ( 832 1393 928 ) bolt14 -128 48 0 1 1
( 1072 1776 1248 ) ( 1072 1777 1248 ) ( 1073 1776 1248 ) bolt14 -128 48 0 1 1
( 1072 1504 944 ) ( 1073 1504 944 ) ( 1072 1504 945 ) bolt14 -128 32 0 1 1
( 1056 1392 928 ) ( 1056 1392 929 ) ( 1056 1393 928 ) bolt14 -48 32 0 1 1
}
// brush 4
{
( 1056 1088 1056 ) ( 1056 1089 1056 ) ( 1056 1088 1057 ) bolt14 0 32 0 1 1
( 736 1088 1056 ) ( 736 1088 1057 ) ( 737 1088 1056 ) bolt14 -32 32 0 1 1
( 736 1088 928 ) ( 737 1088 928 ) ( 736 1089 928 ) bolt14 -32 0 0 1 1
( 976 1472 1248 ) ( 976 1473 1248 ) ( 977 1472 1248 ) bolt14 -32 0 0 1 1
( 976 1488 1072 ) ( 977 1488 1072 ) ( 976 1488 1073 ) bolt14 -32 32 0 1 1
( 1072 1472 1072 ) ( 1072 1472 1073 ) ( 1072 1473 1072 ) bolt14 0 32 0 1 1
}
// brush 5
{
( 464 1088 1056 ) ( 464 1089 1056 ) ( 464 1088 1057 ) bolt14 0 32 0 1 1
( 144 1072 1056 ) ( 144 1072 1057 ) ( 145 1072 1056 ) bolt14 48 32 0 1 1
( 144 1088 928 ) ( 145 1088 928 ) ( 144 1089 928 ) bolt14 48 0 0 1 1
( 384 1472 1248 ) ( 384 1473 1248 ) ( 385 1472 1248 ) bolt14 48 0 0 1 1
( 384 1488 1072 ) ( 385 1488 1072 ) ( 384 1488 1073 ) bolt14 48 32 0 1 1
( 480 1472 1072 ) ( 480 1472 1073 ) ( 480 1473 1072 ) bolt14 0 32 0 1 1
}
// brush 6
{
( 704 1088 944 ) ( 704 1089 944 ) ( 704 1088 945 ) bolt10 0 0 0 1 1
( 704 1088 944 ) ( 704 1088 945 ) ( 705 1088 944 ) bolt10 0 0 0 1 1
( 704 1088 944 ) ( 705 1088 944 ) ( 704 1089 944 ) bolt10 0 0 0 1 1
( 720 1216 1008 ) ( 720 1217 1008 ) ( 721 1216 1008 ) bolt10 0 0 0 1 1
( 720 1216 960 ) ( 721 1216 960 ) ( 720 1216 961 ) bolt10 0 0 0 1 1
( 720 1216 960 ) ( 720 1216 961 ) ( 720 1217 960 ) bolt10 0 0 0 1 1
}
// brush 7
{
( 704 1088 944 ) ( 704 1089 944 ) ( 704 1088 945 ) bolt10 0 0 0 1 1
( 720 1344 960 ) ( 720 1344 961 ) ( 721 1344 960 ) bolt10 0 0 0 1 1
( 704 1088 944 ) ( 705 1088 944 ) ( 704 1089 944 ) bolt10 0 0 0 1 1
( 720 1216 1008 ) ( 720 1217 1008 ) ( 721 1216 1008 ) bolt10 0 0 0 1 1
( 720 1488 960 ) ( 721 1488 960 ) ( 720 1488 961 ) bolt10 0 0 0 1 1
( 720 1216 960 ) ( 720 1216 961 ) ( 720 1217 960 ) bolt10 0 0 0 1 1
}
// brush 8
{
( 704 1088 944 ) ( 704 1089 944 ) ( 704 1088 945 ) bolt10 0 0 0 1 1
( 704 1088 944 ) ( 704 1088 945 ) ( 705 1088 944 ) bolt10 0 0 0 1 1
( 720 1216 1008 ) ( 721 1216 1008 ) ( 720 1217 1008 ) bolt10 0 0 0 1 1
( 720 1216 1016 ) ( 720 1217 1016 ) ( 721 1216 1016 ) bolt10 0 0 0 1 1
( 720 1488 960 ) ( 721 1488 960 ) ( 720 1488 961 ) bolt10 0 0 0 1 1
( 720 1216 960 ) ( 720 1216 961 ) ( 720 1217 960 ) bolt10 0 0 0 1 1
}
}
// entity 1
{
"classname" "info_player_start"
"origin" "848 1280 968"
"angle" "180"
}
// entity 2
{
"classname" "light"
"origin" "760 1280 1048"
"light" "1000"
"targetname" "toggle_door1_shadow"
"spawnflags" "1"
"_switchableshadow_target" "door1"
}
// entity 3
{
"classname" "light"
"origin" "568 1264 952"
"_color" "1 0 0"
"light" "500"
}
// entity 4
{
"classname" "func_door"
"targetname" "door1"
"spawnflags" "32"
"angle" "-2"
"speed" "1000"
// brush 0
{
( 704 1320 944 ) ( 704 1321 944 ) ( 704 1320 945 ) bolt10 -8 0 0 1 1
( 704 1320 944 ) ( 704 1320 945 ) ( 705 1320 944 ) bolt10 0 0 0 1 1
( 704 1320 944 ) ( 705 1320 944 ) ( 704 1321 944 ) bolt10 0 8 0 1 1
( 720 1448 1008 ) ( 720 1449 1008 ) ( 721 1448 1008 ) bolt10 0 8 0 1 1
( 720 1336 960 ) ( 721 1336 960 ) ( 720 1336 961 ) bolt10 0 0 0 1 1
( 720 1448 960 ) ( 720 1448 961 ) ( 720 1449 960 ) bolt10 -8 0 0 1 1
}
// brush 1
{
( 704 1224 944 ) ( 704 1225 944 ) ( 704 1224 945 ) bolt10 -8 0 0 1 1
( 704 1224 944 ) ( 704 1224 945 ) ( 705 1224 944 ) bolt10 0 0 0 1 1
( 704 1224 944 ) ( 705 1224 944 ) ( 704 1225 944 ) bolt10 0 8 0 1 1
( 720 1352 1008 ) ( 720 1353 1008 ) ( 721 1352 1008 ) bolt10 0 8 0 1 1
( 720 1240 960 ) ( 721 1240 960 ) ( 720 1240 961 ) bolt10 0 0 0 1 1
( 720 1352 960 ) ( 720 1352 961 ) ( 720 1353 960 ) bolt10 -8 0 0 1 1
}
// brush 2
{
( 704 1256 944 ) ( 704 1257 944 ) ( 704 1256 945 ) bolt10 -8 0 0 1 1
( 704 1256 944 ) ( 704 1256 945 ) ( 705 1256 944 ) bolt10 0 0 0 1 1
( 704 1256 944 ) ( 705 1256 944 ) ( 704 1257 944 ) bolt10 0 8 0 1 1
( 720 1384 1008 ) ( 720 1385 1008 ) ( 721 1384 1008 ) bolt10 0 8 0 1 1
( 720 1272 960 ) ( 721 1272 960 ) ( 720 1272 961 ) bolt10 0 0 0 1 1
( 720 1384 960 ) ( 720 1384 961 ) ( 720 1385 960 ) bolt10 -8 0 0 1 1
}
// brush 3
{
( 704 1288 944 ) ( 704 1289 944 ) ( 704 1288 945 ) bolt10 -8 0 0 1 1
( 704 1288 944 ) ( 704 1288 945 ) ( 705 1288 944 ) bolt10 0 0 0 1 1
( 704 1288 944 ) ( 705 1288 944 ) ( 704 1289 944 ) bolt10 0 8 0 1 1
( 720 1416 1008 ) ( 720 1417 1008 ) ( 721 1416 1008 ) bolt10 0 8 0 1 1
( 720 1304 960 ) ( 721 1304 960 ) ( 720 1304 961 ) bolt10 0 0 0 1 1
( 720 1416 960 ) ( 720 1416 961 ) ( 720 1417 960 ) bolt10 -8 0 0 1 1
}
}
// entity 5
{
"classname" "func_button"
"target" "switch1"
"angle" "-2"
// brush 0
{
( 752 1288 952 ) ( 752 1289 952 ) ( 752 1288 953 ) swire2 -56 0 0 1 1
( 792 1272 944 ) ( 791 1272 944 ) ( 792 1272 945 ) swire2 48 0 180 1 -1
( 792 1248 944 ) ( 792 1249 944 ) ( 791 1248 944 ) swire2 -56 -48 90 1 1
( 744 1288 952 ) ( 743 1288 952 ) ( 744 1289 952 ) +0switch -8 -16 90 1 1
( 744 1288 952 ) ( 744 1288 953 ) ( 743 1288 952 ) swire2 48 0 180 1 -1
( 784 1248 944 ) ( 784 1248 945 ) ( 784 1249 944 ) swire2 -56 0 0 1 1
}
}
// entity 6
{
"classname" "trigger_relay"
"origin" "760 1256 1000"
"targetname" "switch1"
"target" "door1"
}
// entity 7
{
"classname" "trigger_relay"
"origin" "760 1320 1000"
"targetname" "switch1"
"target" "toggle_door1_shadow"
}

View File

@ -319,7 +319,7 @@ static void CheckFaceLuxelsNonBlack(const mbsp_t &bsp, const mface_t &face)
static void CheckFaceLuxelAtPoint(const mbsp_t *bsp, const dmodelh2_t *model, const qvec3b &expected_color, static void CheckFaceLuxelAtPoint(const mbsp_t *bsp, const dmodelh2_t *model, const qvec3b &expected_color,
const qvec3d &point, const qvec3d &normal = {0, 0, 0}, const lit_variant_t *lit = nullptr, const qvec3d &point, const qvec3d &normal = {0, 0, 0}, const lit_variant_t *lit = nullptr,
const bspxentries_t *bspx = nullptr) const bspxentries_t *bspx = nullptr, int style = 0)
{ {
auto *face = BSP_FindFaceAtPoint(bsp, model, point, normal); auto *face = BSP_FindFaceAtPoint(bsp, model, point, normal);
ASSERT_TRUE(face); ASSERT_TRUE(face);
@ -340,7 +340,7 @@ static void CheckFaceLuxelAtPoint(const mbsp_t *bsp, const dmodelh2_t *model, co
const auto coord = extents.worldToLMCoord(point); const auto coord = extents.worldToLMCoord(point);
const auto int_coord = qvec2i(round(coord[0]), round(coord[1])); const auto int_coord = qvec2i(round(coord[0]), round(coord[1]));
const qvec3b sample = LM_Sample(bsp, face, lit, extents, offset, int_coord); const qvec3b sample = LM_Sample(bsp, face, lit, extents, offset, int_coord, style);
SCOPED_TRACE(fmt::format("world point: {}", point)); SCOPED_TRACE(fmt::format("world point: {}", point));
SCOPED_TRACE(fmt::format("lm coord: {}", coord)); SCOPED_TRACE(fmt::format("lm coord: {}", coord));
SCOPED_TRACE(fmt::format("lm int_coord: {}", int_coord)); SCOPED_TRACE(fmt::format("lm int_coord: {}", int_coord));
@ -1145,4 +1145,35 @@ TEST(ltfaceQ1, hdr)
// check internal lightmap - greyscale, since Q1 // check internal lightmap - greyscale, since Q1
CheckFaceLuxelAtPoint(&bsp, &bsp.dmodels[0], {0, 0, 0}, testpoint, testnormal); CheckFaceLuxelAtPoint(&bsp, &bsp.dmodels[0], {0, 0, 0}, testpoint, testnormal);
} }
}
TEST(ltfaceQ1, switchableshadowTarget)
{
SCOPED_TRACE("Vanilla-compatible switchable shadows");
auto [bsp, bspx, lit] = QbspVisLight_Q1("q1_light_switchableshadow_target.map", {});
// find the light controlling the switchable shadow
auto entdicts = EntData_Parse(bsp);
auto it = std::find_if(entdicts.begin(), entdicts.end(), [](const entdict_t& dict) -> bool {
return dict.get("_switchableshadow_target") == "door1";
});
ASSERT_NE(it, entdicts.end());
ASSERT_TRUE(it->has("style"));
int switchable_style = it->get_int("style");
ASSERT_EQ(32, switchable_style);
const qvec3f not_in_shadow {792, 1240, 944};
const qvec3f in_shadow {792, 1264, 944};
// not in shadow - should be lit up red in style 0, and black in style 32
CheckFaceLuxelAtPoint(&bsp, &bsp.dmodels[0], {68, 0, 0}, not_in_shadow, {0, 0, 1}, &lit, &bspx, 0);
CheckFaceLuxelAtPoint(&bsp, &bsp.dmodels[0], {0, 0, 0}, not_in_shadow, {0, 0, 1}, &lit, &bspx, 32);
// in (switchable) shadow - should be black in style 0, and red in style 32
CheckFaceLuxelAtPoint(&bsp, &bsp.dmodels[0], {0, 0, 0}, in_shadow, {0, 0, 1}, &lit, &bspx, 0);
CheckFaceLuxelAtPoint(&bsp, &bsp.dmodels[0], {68, 0, 0}, in_shadow, {0, 0, 1}, &lit, &bspx, 32);
} }