diff --git a/include/common/bspfile.hh b/include/common/bspfile.hh index df4f3d92..421a7c4f 100644 --- a/include/common/bspfile.hh +++ b/include/common/bspfile.hh @@ -176,7 +176,8 @@ struct surfflags_t // light doesn't bounce off this face bool no_bounce; - // opt out of minlight on this face + // opt out of minlight on this face (including opting out of local minlight, so + // not the same as just setting minlight to 0). bool no_minlight; // don't expand this face for larger clip hulls @@ -210,8 +211,8 @@ struct surfflags_t // _phong_group key, equivalent q2 map format's use of the "value" field int phong_group; - // minlight value for this face - vec_t minlight; + // minlight value for this face. empty = inherit from worldspawn. + std::optional minlight; // red minlight colors for this face qvec3b minlight_color; diff --git a/light/ltface.cc b/light/ltface.cc index 676a87be..b93016a8 100644 --- a/light/ltface.cc +++ b/light/ltface.cc @@ -615,8 +615,10 @@ static std::unique_ptr Lightsurf_Init(const modelinfo_t *modelinfo, // minlight if (modelinfo->minlight.is_changed()) { lightsurf->minlight = modelinfo->minlight.value(); + } else if (extended_flags.minlight) { + lightsurf->minlight = *extended_flags.minlight; } else { - lightsurf->minlight = extended_flags.minlight; + lightsurf->minlight = light_options.minlight.value(); } // minlightMottle diff --git a/qbsp/map.cc b/qbsp/map.cc index a9e9ea3e..37f38460 100644 --- a/qbsp/map.cc +++ b/qbsp/map.cc @@ -740,11 +740,10 @@ static surfflags_t SurfFlagsForEntity( flags.phong_group = entity.epairs.get_int("_phong_group"); // handle "_minlight" - const vec_t minlight = entity.epairs.get_float("_minlight"); - if (minlight > 0) { - // CHECK: allow > 510 now that we're float? or is it not worth it since it will - // be beyond max? - flags.minlight = clamp(minlight, 0.0, 510.0); + if (entity.epairs.has("_minlight")) { + const vec_t minlight = entity.epairs.get_float("_minlight"); + // handle -1 as an alias for 0 (same with other negative values). + flags.minlight = max(0., minlight); } // handle "_maxlight" diff --git a/qbsp/writebsp.cc b/qbsp/writebsp.cc index 305d1c79..dbd1d763 100644 --- a/qbsp/writebsp.cc +++ b/qbsp/writebsp.cc @@ -403,7 +403,7 @@ static void WriteExtendedTexinfoFlags(void) t["phong_group"] = tx.flags.phong_group; } if (tx.flags.minlight) { - t["minlight"] = tx.flags.minlight; + t["minlight"] = *tx.flags.minlight; } if (tx.flags.maxlight) { t["maxlight"] = tx.flags.maxlight; diff --git a/testmaps/q2_light_group.map b/testmaps/q2_light_group.map index d4baa8ee..2e4bf699 100644 --- a/testmaps/q2_light_group.map +++ b/testmaps/q2_light_group.map @@ -145,6 +145,7 @@ { "classname" "func_wall" "_object_channel_mask" "2" +"_minlight" "0" // brush 0 { ( 656 1280 944 ) ( 656 1281 944 ) ( 656 1280 945 ) e1u1/twall2_1 -32 0 0 1 1 @@ -177,6 +178,7 @@ { "classname" "func_wall" "_object_channel_mask" "4" +"_minlight" "0" // brush 0 { ( 880 1248 944 ) ( 880 1249 944 ) ( 880 1248 945 ) e1u1/twall2_1 0 0 0 1 1 @@ -208,6 +210,7 @@ "classname" "func_wall" "_object_channel_mask" "8" "_shadow" "1" +"_minlight" "0" // brush 0 { ( 1264 1248 944 ) ( 1264 1249 944 ) ( 1264 1248 945 ) e1u1/twall2_1 0 0 0 1 1 @@ -230,6 +233,7 @@ { "classname" "func_group" "_object_channel_mask" "8" +"_minlight" "0" // brush 0 { ( 1456 1248 944 ) ( 1456 1249 944 ) ( 1456 1248 945 ) e1u1/twall2_1 0 0 0 1 1 diff --git a/testmaps/q2_minlight_inherited.map b/testmaps/q2_minlight_inherited.map new file mode 100644 index 00000000..9e228f92 --- /dev/null +++ b/testmaps/q2_minlight_inherited.map @@ -0,0 +1,96 @@ +// Game: Quake 2 +// Format: Quake2 +// entity 0 +{ +"classname" "worldspawn" +"_tb_textures" "textures/e1u1" +"_bounce" "0" +"_minlight" "0.5" +"_minlight_mottle" "0" +// brush 0 +{ +( -480 -176 -16 ) ( -480 -175 -16 ) ( -480 -176 -15 ) e1u1/twall2_1 -16 112 0 1 1 +( -256 -176 -16 ) ( -256 -176 -15 ) ( -255 -176 -16 ) e1u1/twall2_1 64 112 0 1 1 +( -256 -176 -16 ) ( -255 -176 -16 ) ( -256 -175 -16 ) e1u1/twall2_1 64 16 0 1 1 +( -16 208 0 ) ( -16 209 0 ) ( -15 208 0 ) e1u1/twall2_1 64 16 0 1 1 +( -16 224 0 ) ( -15 224 0 ) ( -16 224 1 ) e1u1/twall2_1 64 112 0 1 1 +( 480 208 0 ) ( 480 208 1 ) ( 480 209 0 ) e1u1/twall2_1 -16 112 0 1 1 +} +// brush 1 +{ +( -480 -176 304 ) ( -480 -175 304 ) ( -480 -176 305 ) e1u1/twall2_1 64 16 0 1 1 +( -256 -192 304 ) ( -256 -192 305 ) ( -255 -192 304 ) e1u1/twall2_1 64 16 0 1 1 +( -256 -176 304 ) ( -255 -176 304 ) ( -256 -175 304 ) e1u1/twall2_1 64 16 0 1 1 +( -16 208 320 ) ( -16 209 320 ) ( -15 208 320 ) e1u1/twall2_1 64 16 0 1 1 +( -16 224 320 ) ( -15 224 320 ) ( -16 224 321 ) e1u1/twall2_1 64 16 0 1 1 +( 480 208 320 ) ( 480 208 321 ) ( 480 209 320 ) e1u1/twall2_1 64 16 0 1 1 +} +// brush 2 +{ +( -480 -192 -16 ) ( -480 -191 -16 ) ( -480 -192 -15 ) e1u1/twall2_1 64 16 0 1 1 +( -256 -192 -16 ) ( -256 -192 -15 ) ( -255 -192 -16 ) e1u1/twall2_1 64 16 0 1 1 +( -256 -192 -16 ) ( -255 -192 -16 ) ( -256 -191 -16 ) e1u1/twall2_1 64 16 0 1 1 +( -16 192 304 ) ( -16 193 304 ) ( -15 192 304 ) e1u1/twall2_1 64 16 0 1 1 +( -16 -176 0 ) ( -15 -176 0 ) ( -16 -176 1 ) e1u1/twall2_1 64 16 0 1 1 +( 496 192 0 ) ( 496 192 1 ) ( 496 193 0 ) e1u1/twall2_1 64 16 0 1 1 +} +// brush 3 +{ +( -480 128 -16 ) ( -480 129 -16 ) ( -480 128 -15 ) e1u1/twall2_1 64 16 0 1 1 +( -128 224 -16 ) ( -128 224 -15 ) ( -127 224 -16 ) e1u1/twall2_1 64 16 0 1 1 +( -128 128 -16 ) ( -127 128 -16 ) ( -128 129 -16 ) e1u1/twall2_1 64 16 0 1 1 +( 112 512 304 ) ( 112 513 304 ) ( 113 512 304 ) e1u1/twall2_1 64 16 0 1 1 +( 112 240 0 ) ( 113 240 0 ) ( 112 240 1 ) e1u1/twall2_1 64 16 0 1 1 +( 480 128 -16 ) ( 480 128 -15 ) ( 480 129 -16 ) e1u1/twall2_1 64 16 0 1 1 +} +// brush 4 +{ +( 480 -176 112 ) ( 480 -175 112 ) ( 480 -176 113 ) e1u1/twall2_1 64 16 0 1 1 +( 160 -176 112 ) ( 160 -176 113 ) ( 161 -176 112 ) e1u1/twall2_1 64 16 0 1 1 +( 160 -176 -16 ) ( 161 -176 -16 ) ( 160 -175 -16 ) e1u1/twall2_1 64 16 0 1 1 +( 400 208 304 ) ( 400 209 304 ) ( 401 208 304 ) e1u1/twall2_1 64 16 0 1 1 +( 400 224 128 ) ( 401 224 128 ) ( 400 224 129 ) e1u1/twall2_1 64 16 0 1 1 +( 496 208 128 ) ( 496 208 129 ) ( 496 209 128 ) e1u1/twall2_1 64 16 0 1 1 +} +// brush 5 +{ +( -496 -176 112 ) ( -496 -175 112 ) ( -496 -176 113 ) e1u1/twall2_1 64 16 0 1 1 +( -816 -192 112 ) ( -816 -192 113 ) ( -815 -192 112 ) e1u1/twall2_1 64 16 0 1 1 +( -816 -176 -16 ) ( -815 -176 -16 ) ( -816 -175 -16 ) e1u1/twall2_1 64 16 0 1 1 +( -576 208 304 ) ( -576 209 304 ) ( -575 208 304 ) e1u1/twall2_1 64 16 0 1 1 +( -576 224 128 ) ( -575 224 128 ) ( -576 224 129 ) e1u1/twall2_1 64 16 0 1 1 +( -480 208 128 ) ( -480 208 129 ) ( -480 209 128 ) e1u1/twall2_1 64 16 0 1 1 +} +} +// entity 1 +{ +"classname" "info_player_start" +"origin" "272 -104 24" +"angle" "90" +} +// entity 2 +{ +"classname" "func_group" +// brush 0 +{ +( 288 0 0 ) ( 288 1 0 ) ( 288 0 1 ) e1u1/twall2_1 -16 16 0 1 1 +( 176 0 0 ) ( 176 0 1 ) ( 177 0 0 ) e1u1/twall2_1 0 16 0 1 1 +( 176 0 0 ) ( 177 0 0 ) ( 176 1 0 ) e1u1/twall2_1 0 -32 0 1 1 +( 416 144 16 ) ( 416 145 16 ) ( 417 144 16 ) e1u1/twall2_1 0 -32 0 1 1 +( 416 144 16 ) ( 417 144 16 ) ( 416 144 17 ) e1u1/twall2_1 0 16 0 1 1 +( 416 144 16 ) ( 416 144 17 ) ( 416 145 16 ) e1u1/twall2_1 -16 16 0 1 1 +} +} +// entity 3 +{ +"classname" "func_wall" +// brush 0 +{ +( 144 0 0 ) ( 144 1 0 ) ( 144 0 1 ) e1u1/twall2_1 -16 16 0 1 1 +( 32 0 0 ) ( 32 0 1 ) ( 33 0 0 ) e1u1/twall2_1 16 16 0 1 1 +( 32 0 0 ) ( 33 0 0 ) ( 32 1 0 ) e1u1/twall2_1 16 -32 0 1 1 +( 272 144 16 ) ( 272 145 16 ) ( 273 144 16 ) e1u1/twall2_1 16 -32 0 1 1 +( 272 144 16 ) ( 273 144 16 ) ( 272 144 17 ) e1u1/twall2_1 16 16 0 1 1 +( 272 144 16 ) ( 272 144 17 ) ( 272 145 16 ) e1u1/twall2_1 -16 16 0 1 1 +} +} diff --git a/tests/test_ltface.cc b/tests/test_ltface.cc index 699621a0..bcc38827 100644 --- a/tests/test_ltface.cc +++ b/tests/test_ltface.cc @@ -681,4 +681,18 @@ TEST_CASE("q2_light_low_luxel_res2" * doctest::may_fail()) CheckFaceLuxelAtPoint(&bsp, &bsp.dmodels[0], {49, 49, 49}, {2964, 1046, -694}, {-1, 0, 0}, nullptr, &bspx); CheckFaceLuxelAtPoint(&bsp, &bsp.dmodels[0], {25, 25, 25}, {2964, 1046, -706}, {-1, 0, 0}, nullptr, &bspx); CheckFaceLuxelAtPoint(&bsp, &bsp.dmodels[0], {1, 1, 1}, {2964, 1046, -716}, {-1, 0, 0}, nullptr, &bspx); -} \ No newline at end of file +} + +TEST_CASE("q2_minlight_inherited") +{ + auto [bsp, bspx] = QbspVisLight_Q2("q2_minlight_inherited.map", {}); + + { + INFO("check that func_group inherits worldspawn minlight"); + CheckFaceLuxelAtPoint(&bsp, &bsp.dmodels[0], {64, 64, 64}, {360, 72, 16}, {0, 0, 1}, nullptr, &bspx); + } + { + INFO("check that func_wall inherits worldspawn minlight"); + CheckFaceLuxelAtPoint(&bsp, &bsp.dmodels[1], {64, 64, 64}, {208, 72, 16}, {0, 0, 1}, nullptr, &bspx); + } +}