Skip to content

Commit

Permalink
light: add _switchableshadow_target
Browse files Browse the repository at this point in the history
Switchable shadows setup which works in id1

Fixes #432
  • Loading branch information
ericwa committed Jul 21, 2024
1 parent 8ac82b7 commit e5f01ce
Show file tree
Hide file tree
Showing 8 changed files with 275 additions and 7 deletions.
6 changes: 3 additions & 3 deletions common/bsputils.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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,
int byte_offset_of_face, qvec2i coord)
int byte_offset_of_face, qvec2i coord, int style)
{
if (byte_offset_of_face == -1) {
return {0, 0, 0};
Expand All @@ -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[1] < faceextents.height());

int style_offset = StyleOffset(0, face, faceextents);
int style_offset = StyleOffset(style, face, faceextents);
if (style_offset == -1) {
return {0, 0, 0};
}
Expand Down
20 changes: 20 additions & 0 deletions docs/light.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
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"

For brush models, -1 prevents dirtmapping on the brush model. Useful
Expand Down Expand Up @@ -918,6 +923,21 @@ Point Lights
Set to 1 to make the light compiler ignore this entity (prevents it
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
----------

Expand Down
2 changes: 1 addition & 1 deletion include/common/bsputils.hh
Original file line number Diff line number Diff line change
Expand Up @@ -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,
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,
const mface_t *face,
Expand Down
5 changes: 5 additions & 0 deletions include/light/entities.hh
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ public:
settings::setting_int32 light_channel_mask;
settings::setting_int32 shadow_channel_mask;
settings::setting_bool nonudge;
settings::setting_string switchableshadow_target;

light_t();

Expand Down Expand Up @@ -143,6 +144,10 @@ std::vector<std::unique_ptr<light_t>> &GetLights();
const std::vector<entdict_t> &GetEntdicts();
std::vector<sun_t> &GetSuns();
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();

Expand Down
23 changes: 22 additions & 1 deletion light/entities.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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::ofstream surflights_dump_file;
static fs::path surflights_dump_filename;
static std::map<std::string, light_t*> lights_by_switchableshadow_target;

/**
* Resets global data in this file
Expand All @@ -58,6 +59,7 @@ void ResetLightEntities()
surfacelight_templates.clear();
surflights_dump_file = {};
surflights_dump_filename.clear();
lights_by_switchableshadow_target.clear();
}

std::vector<std::unique_ptr<light_t>> &GetLights()
Expand All @@ -80,6 +82,16 @@ std::vector<entdict_t> &GetRadLights()
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 */
static void MakeSurfaceLights(const mbsp_t *bsp);

Expand Down Expand Up @@ -121,7 +133,8 @@ light_t::light_t()
surflight_atten{this, "surflight_atten", 1.f},
light_channel_mask{this, "light_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", ""}
{
}

Expand Down Expand Up @@ -1062,6 +1075,14 @@ void LoadEntities(const settings::worldspawn_keys &cfg, const mbsp_t *bsp)
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());
}
}
Expand Down
7 changes: 7 additions & 0 deletions light/light.cc
Original file line number Diff line number Diff line change
Expand Up @@ -725,6 +725,13 @@ static void FindModelInfo(const mbsp_t *bsp)
// apply settings
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) */
if (info->switchableshadow.boolValue()) {
Q_assert(info->switchshadstyle.value() != 0);
Expand Down
184 changes: 184 additions & 0 deletions testmaps/q1_light_switchableshadow_target.map
Original file line number Diff line number Diff line change
@@ -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"
}
35 changes: 33 additions & 2 deletions tests/test_ltface.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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,
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);
ASSERT_TRUE(face);
Expand All @@ -340,7 +340,7 @@ static void CheckFaceLuxelAtPoint(const mbsp_t *bsp, const dmodelh2_t *model, co
const auto coord = extents.worldToLMCoord(point);
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("lm coord: {}", coord));
SCOPED_TRACE(fmt::format("lm int_coord: {}", int_coord));
Expand Down Expand Up @@ -1145,4 +1145,35 @@ TEST(ltfaceQ1, hdr)
// check internal lightmap - greyscale, since Q1
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);
}

0 comments on commit e5f01ce

Please sign in to comment.