From d7acd046c32071470e5c8ba68ede5a4b2eaab7bc Mon Sep 17 00:00:00 2001 From: Eric Wasylishen Date: Sat, 12 Nov 2022 17:07:02 -0700 Subject: [PATCH] light/qbsp: add _surflight_rescale key --- common/bspfile.cc | 4 +- common/bsputils.cc | 38 +++++++++++++ include/common/bspfile.hh | 4 ++ include/common/bsputils.hh | 5 ++ light/light.cc | 3 ++ light/surflight.cc | 4 +- qbsp/map.cc | 2 + qbsp/writebsp.cc | 3 ++ testmaps/light_q2_emissive_cube.map | 82 +++++++++++++++++++++++++++++ tests/test_ltface.cc | 53 ++++++++++++++++++- 10 files changed, 193 insertions(+), 5 deletions(-) create mode 100644 testmaps/light_q2_emissive_cube.map diff --git a/common/bspfile.cc b/common/bspfile.cc index 9662ec5b..b9fa51c0 100644 --- a/common/bspfile.cc +++ b/common/bspfile.cc @@ -1693,14 +1693,14 @@ const bspversion_t bspver_qbism{Q2_QBISMIDENT, Q2_BSPVERSION, "qbism", "Quake II bool surfflags_t::needs_write() const { - return no_dirt || no_shadow || no_bounce || no_minlight || no_expand || light_ignore || phong_angle || + return no_dirt || no_shadow || no_bounce || no_minlight || no_expand || light_ignore || !surflight_rescale || phong_angle || phong_angle_concave || minlight || !qv::emptyExact(minlight_color) || light_alpha || maxlight || lightcolorscale != 1.0; } static auto as_tuple(const surfflags_t &flags) { return std::tie(flags.native, flags.is_nodraw, flags.is_hintskip, flags.is_hint, flags.no_dirt, flags.no_shadow, flags.no_bounce, flags.no_minlight, flags.no_expand, - flags.light_ignore, flags.phong_angle, flags.phong_angle_concave, flags.minlight, flags.minlight_color, flags.light_alpha, flags.maxlight, flags.lightcolorscale); + flags.light_ignore, flags.surflight_rescale, flags.phong_angle, flags.phong_angle_concave, flags.minlight, flags.minlight_color, flags.light_alpha, flags.maxlight, flags.lightcolorscale); } bool surfflags_t::operator<(const surfflags_t &other) const diff --git a/common/bsputils.cc b/common/bsputils.cc index 3182bf50..f3416c5e 100644 --- a/common/bsputils.cc +++ b/common/bsputils.cc @@ -622,6 +622,20 @@ void DecompressRow(const uint8_t *in, const int numbytes, uint8_t *decompressed) } while (out - decompressed < row); } +bspx_decoupled_lm_perface BSPX_DecoupledLM(const bspxentries_t &entries, int face_num) +{ + auto &lump_bytes = entries.at("DECOUPLED_LM"); + + auto stream = imemstream(lump_bytes.data(), lump_bytes.size()); + + stream.seekg(face_num * sizeof(bspx_decoupled_lm_perface)); + stream >> endianness; + + bspx_decoupled_lm_perface result; + stream >= result; + return result; +} + qvec2d WorldToTexCoord(const qvec3d &world, const mtexinfo_t *tex) { /* @@ -901,3 +915,27 @@ qvec3f faceextents_t::LMCoordToWorld(qvec2f lm) const const qvec4f res = lmToWorldMatrix * lmPadded; return res; } + +/** + * Samples the lightmap at an integer coordinate + */ +qvec3b LM_Sample(const mbsp_t *bsp, const faceextents_t &faceextents, int byte_offset_of_face, qvec2i coord) +{ + int pixel = coord[0] + (coord[1] * faceextents.width()); + + const uint8_t* data = bsp->dlightdata.data(); + + if (bsp->loadversion->game->has_rgb_lightmap) { + return qvec3f{ + data[byte_offset_of_face + (pixel * 3) + 0], + data[byte_offset_of_face + (pixel * 3) + 1], + data[byte_offset_of_face + (pixel * 3) + 2] + }; + } else { + return qvec3f{ + data[byte_offset_of_face + pixel], + data[byte_offset_of_face + pixel], + data[byte_offset_of_face + pixel] + }; + } +} \ No newline at end of file diff --git a/include/common/bspfile.hh b/include/common/bspfile.hh index b395fa73..86645554 100644 --- a/include/common/bspfile.hh +++ b/include/common/bspfile.hh @@ -185,6 +185,10 @@ struct surfflags_t // this face doesn't receive light bool light_ignore; + // if true, rescales any surface light emitted by these brushes to emit 50% light at 90 degrees from the surface normal + // if false, use a more natural angle falloff of 0% at 90 degrees + bool surflight_rescale = true; + // if non zero, enables phong shading and gives the angle threshold to use vec_t phong_angle; diff --git a/include/common/bsputils.hh b/include/common/bsputils.hh index f28cd115..f28876e7 100644 --- a/include/common/bsputils.hh +++ b/include/common/bsputils.hh @@ -20,6 +20,7 @@ #pragma once #include +#include #include #include #include @@ -107,6 +108,8 @@ void Face_DebugPrint(const mbsp_t *bsp, const mface_t *face); void CompressRow(const uint8_t *vis, const size_t numbytes, std::back_insert_iterator> it); void DecompressRow(const uint8_t *in, const int numbytes, uint8_t *decompressed); +bspx_decoupled_lm_perface BSPX_DecoupledLM(const bspxentries_t &entries, int face_num); + /* ======================================================================== */ qvec2d WorldToTexCoord(const qvec3d &world, const mtexinfo_t *tex); @@ -150,3 +153,5 @@ public: qvec2f worldToLMCoord(qvec3f world) const; qvec3f LMCoordToWorld(qvec2f lm) const; }; + +qvec3b LM_Sample(const mbsp_t *bsp, const faceextents_t &faceextents, int byte_offset_of_face, qvec2i coord); diff --git a/light/light.cc b/light/light.cc index a96a9cd2..f1adeb96 100644 --- a/light/light.cc +++ b/light/light.cc @@ -1072,6 +1072,9 @@ static void LoadExtendedTexinfoFlags(const fs::path &sourcefilename, const mbsp_ if (val.contains("light_ignore")) { flags.light_ignore = val.at("light_ignore").get(); } + if (val.contains("surflight_rescale")) { + flags.surflight_rescale = val.at("surflight_rescale").get(); + } if (val.contains("phong_angle")) { flags.phong_angle = val.at("phong_angle").get(); } diff --git a/light/surflight.cc b/light/surflight.cc index 09288f4a..0436022f 100644 --- a/light/surflight.cc +++ b/light/surflight.cc @@ -61,6 +61,8 @@ static void MakeSurfaceLight(const mbsp_t *bsp, const settings::worldspawn_keys auto poly = GLM_FacePoints(bsp, face); const float facearea = qv::PolyArea(poly.begin(), poly.end()); + const surfflags_t &extended_flags = extended_texinfo_flags[face->texinfo]; + // Avoid small, or zero-area faces if (facearea < 1) return; @@ -115,7 +117,7 @@ static void MakeSurfaceLight(const mbsp_t *bsp, const settings::worldspawn_keys l.omnidirectional = !is_directional; l.points = std::move(points); l.style = style; - l.rescale = true; + l.rescale = extended_flags.surflight_rescale; // Init bbox... if (light_options.visapprox.value() == visapprox_t::RAYS) { diff --git a/qbsp/map.cc b/qbsp/map.cc index 9233f344..b65e6e5c 100644 --- a/qbsp/map.cc +++ b/qbsp/map.cc @@ -576,6 +576,8 @@ static surfflags_t SurfFlagsForEntity(const maptexinfo_t &texinfo, const mapenti flags.no_minlight = true; if (entity.epairs.get_int("_lightignore") == 1) flags.light_ignore = true; + if (entity.epairs.has("_surflight_rescale") && entity.epairs.get_int("_surflight_rescale") == 0) + flags.surflight_rescale = false; // "_minlight_exclude", "_minlight_exclude2", "_minlight_exclude3"... for (int i = 0; i <= 9; i++) { diff --git a/qbsp/writebsp.cc b/qbsp/writebsp.cc index 8574b339..297e4323 100644 --- a/qbsp/writebsp.cc +++ b/qbsp/writebsp.cc @@ -368,6 +368,9 @@ static void WriteExtendedTexinfoFlags(void) if (tx.flags.light_ignore) { t["light_ignore"] = tx.flags.light_ignore; } + if (tx.flags.surflight_rescale == false) { + t["surflight_rescale"] = tx.flags.surflight_rescale; + } if (tx.flags.phong_angle) { t["phong_angle"] = tx.flags.phong_angle; } diff --git a/testmaps/light_q2_emissive_cube.map b/testmaps/light_q2_emissive_cube.map new file mode 100644 index 00000000..49891712 --- /dev/null +++ b/testmaps/light_q2_emissive_cube.map @@ -0,0 +1,82 @@ +// Game: Quake 2 +// Format: Quake2 (Valve) +// entity 0 +{ +"classname" "worldspawn" +"_tb_textures" "textures/e1u1" +"_bounce" "0" +// brush 0 +{ +( 928 -1072 880 ) ( 928 -1504 880 ) ( 928 -1504 864 ) e1u1/box3_7 [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0 +( 928 -1504 880 ) ( 1120 -1504 880 ) ( 1120 -1504 864 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0 +( 1120 -1504 864 ) ( 1120 -1072 864 ) ( 928 -1072 864 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1 0 0 0 +( 928 -1072 880 ) ( 1120 -1072 880 ) ( 1120 -1504 880 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1 0 0 0 +( 1120 -1072 864 ) ( 1120 -1072 880 ) ( 928 -1072 880 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0 +( 1120 -1504 880 ) ( 1120 -1072 880 ) ( 1120 -1072 864 ) e1u1/box3_7 [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0 +} +// brush 1 +{ +( 928 -1072 1040 ) ( 928 -1504 1040 ) ( 928 -1504 1024 ) e1u1/box3_7 [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0 +( 928 -1504 1040 ) ( 1120 -1504 1040 ) ( 1120 -1504 1024 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0 +( 1120 -1504 1024 ) ( 1120 -1072 1024 ) ( 928 -1072 1024 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1 0 0 0 +( 928 -1072 1040 ) ( 1120 -1072 1040 ) ( 1120 -1504 1040 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1 0 0 0 +( 1120 -1072 1024 ) ( 1120 -1072 1040 ) ( 928 -1072 1040 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0 +( 1120 -1504 1040 ) ( 1120 -1072 1040 ) ( 1120 -1072 1024 ) e1u1/box3_7 [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0 +} +// brush 2 +{ +( 1120 -1072 1024 ) ( 1120 -1504 1024 ) ( 1120 -1504 880 ) e1u1/box3_7 [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0 +( 1120 -1504 1024 ) ( 1136 -1504 1024 ) ( 1136 -1504 880 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0 +( 1136 -1504 880 ) ( 1136 -1072 880 ) ( 1120 -1072 880 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1 0 0 0 +( 1120 -1072 1024 ) ( 1136 -1072 1024 ) ( 1136 -1504 1024 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1 0 0 0 +( 1136 -1072 880 ) ( 1136 -1072 1024 ) ( 1120 -1072 1024 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0 +( 1136 -1504 1024 ) ( 1136 -1072 1024 ) ( 1136 -1072 880 ) e1u1/box3_7 [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0 +} +// brush 3 +{ +( 928 -1072 1024 ) ( 928 -1504 1024 ) ( 928 -1504 880 ) e1u1/box3_7 [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0 +( 928 -1504 1024 ) ( 944 -1504 1024 ) ( 944 -1504 880 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0 +( 944 -1504 880 ) ( 944 -1072 880 ) ( 928 -1072 880 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1 0 0 0 +( 928 -1072 1024 ) ( 944 -1072 1024 ) ( 944 -1504 1024 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1 0 0 0 +( 944 -1072 880 ) ( 944 -1072 1024 ) ( 928 -1072 1024 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0 +( 944 -1504 1024 ) ( 944 -1072 1024 ) ( 944 -1072 880 ) e1u1/box3_7 [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0 +} +// brush 4 +{ +( 944 -1072 1024 ) ( 944 -1088 1024 ) ( 944 -1088 880 ) e1u1/box3_7 [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0 +( 944 -1088 1024 ) ( 1120 -1088 1024 ) ( 1120 -1088 880 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0 +( 1120 -1088 880 ) ( 1120 -1072 880 ) ( 944 -1072 880 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1 0 0 0 +( 944 -1072 1024 ) ( 1120 -1072 1024 ) ( 1120 -1088 1024 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1 0 0 0 +( 1120 -1072 880 ) ( 1120 -1072 1024 ) ( 944 -1072 1024 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0 +( 1120 -1088 1024 ) ( 1120 -1072 1024 ) ( 1120 -1072 880 ) e1u1/box3_7 [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0 +} +// brush 5 +{ +( 936 -1504 1024 ) ( 936 -1520 1024 ) ( 936 -1520 880 ) e1u1/box3_7 [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0 +( 936 -1520 1024 ) ( 1112 -1520 1024 ) ( 1112 -1520 880 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0 +( 1112 -1520 880 ) ( 1112 -1504 880 ) ( 936 -1504 880 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1 0 0 0 +( 936 -1504 1024 ) ( 1112 -1504 1024 ) ( 1112 -1520 1024 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1 0 0 0 +( 1112 -1504 880 ) ( 1112 -1504 1024 ) ( 936 -1504 1024 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0 +( 1112 -1520 1024 ) ( 1112 -1504 1024 ) ( 1112 -1504 880 ) e1u1/box3_7 [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0 +} +} +// entity 1 +{ +"classname" "info_player_start" +"origin" "1040 -1184 904" +"angle" "270" +} +// entity 2 +{ +"classname" "func_group" +"_surflight_rescale" "0" +// brush 0 +{ +( 968 -1264 992 ) ( 968 -1464 992 ) ( 968 -1464 904 ) e1u1/box3_6 [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 1 100 +( 968 -1464 992 ) ( 1104 -1464 992 ) ( 1104 -1464 904 ) e1u1/box3_6 [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 1 100 +( 1104 -1464 904 ) ( 1104 -1264 904 ) ( 968 -1264 904 ) e1u1/box3_6 [ 1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1 0 1 100 +( 968 -1264 992 ) ( 1104 -1264 992 ) ( 1104 -1464 992 ) e1u1/box3_6 [ 1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1 0 1 100 +( 1104 -1264 904 ) ( 1104 -1264 992 ) ( 968 -1264 992 ) e1u1/box3_6 [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 1 100 +( 1104 -1464 992 ) ( 1104 -1264 992 ) ( 1104 -1264 904 ) e1u1/box3_6 [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 1 100 +} +} diff --git a/tests/test_ltface.cc b/tests/test_ltface.cc index b81f0ce9..fee69fd4 100644 --- a/tests/test_ltface.cc +++ b/tests/test_ltface.cc @@ -5,7 +5,12 @@ #include #include -static void LoadTestmap(const std::filesystem::path &name, std::vector extra_args) +struct testresults_t { + mbsp_t bsp; + bspxentries_t bspx; +}; + +static testresults_t LoadTestmap(const std::filesystem::path &name, std::vector extra_args) { auto map_path = std::filesystem::path(testmaps_dir) / name; @@ -52,9 +57,53 @@ static void LoadTestmap(const std::filesystem::path &name, std::vector(bspdata.bsp), fs::path(qbsp_options.bsp_path).replace_extension(".bsp.json")); + + return {std::move(std::get(bspdata.bsp)), + std::move(bspdata.bspx.entries)}; } } TEST_CASE("TestLight") { - LoadTestmap("q2_lightmap_custom_scale.map", {"-threads", "1", "-extra", "-world_units_per_luxel", "8"}); + LoadTestmap("q2_lightmap_custom_scale.map", {"-threads", "1", "-world_units_per_luxel", "8"}); +} + +TEST_CASE("emissive cube artifacts") { + // A cube with surface flags "light", value "100", placed in a hallway. + // + // Generates harsh lines on the walls/ceiling due to a hack in `light` allowing + // surface lights to emit 50% at 90 degrees off their surface normal (when physically it should be 0%). + // + // It's wanted in some cases (base1.map sewer lights flush with the wall, desired for them to + // emit some lights on to their adjacent wall faces.) + // + // To disable the behaviour in this case with the cube lighting a hallway we have a entity key: + // + // "_surflight_rescale" "0" + // + auto [bsp, bspx] = LoadTestmap("light_q2_emissive_cube.map", {"-threads", "1", "-world_units_per_luxel", "4"}); + + const auto start = qvec3d{1044, -1244, 880}; + const auto end = qvec3d{1044, -1272, 880}; + + auto *floor = BSP_FindFaceAtPoint(&bsp, &bsp.dmodels[0], start, {0, 0, 1}); + auto lm_info = BSPX_DecoupledLM(bspx, Face_GetNum(&bsp, floor)); + + const faceextents_t extents(*floor, bsp, lm_info.lmwidth, lm_info.lmheight, lm_info.world_to_lm_space); + + // sample the lightmap along the floor, approaching the glowing cube + // should get brighter + qvec3b previous_sample{}; + for (int y = start[1]; y >= end[1]; y -= 4) { + qvec3d pos = start; + pos[1] = y; + + auto lm_coord = extents.worldToLMCoord(pos); + + auto sample = LM_Sample(&bsp, extents, lm_info.offset, lm_coord); + CHECK(sample[0] >= previous_sample[0]); + + //logging::print("world: {} lm_coord: {} sample: {} lm size: {}x{}\n", pos, lm_coord, sample, lm_info.lmwidth, lm_info.lmheight); + + previous_sample = sample; + } }