From 39a54abfc6ef87e753383d2e46a1d4ed59c0eb97 Mon Sep 17 00:00:00 2001 From: Jonathan Date: Fri, 28 Apr 2023 13:31:50 -0400 Subject: [PATCH] add support for a region brush (code needs to be cleaned up a bit though) allow _surflight_style on brush models --- common/bspfile.cc | 2 +- include/common/bspfile.hh | 3 + include/light/light.hh | 1 + include/qbsp/map.hh | 7 ++ light/entities.cc | 11 +- light/light.cc | 4 + light/surflight.cc | 6 +- qbsp/brush.cc | 9 ++ qbsp/map.cc | 210 +++++++++++++++++++++++++++++--------- qbsp/writebsp.cc | 3 + 10 files changed, 199 insertions(+), 57 deletions(-) diff --git a/common/bspfile.cc b/common/bspfile.cc index 896f0982..befe4f04 100644 --- a/common/bspfile.cc +++ b/common/bspfile.cc @@ -1803,7 +1803,7 @@ 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.no_phong, flags.light_ignore, - flags.surflight_rescale, flags.surflight_color, flags.surflight_minlight_scale, flags.phong_angle, flags.phong_angle_concave, flags.phong_group, flags.minlight, + flags.surflight_rescale, flags.surflight_style, flags.surflight_color, flags.surflight_minlight_scale, flags.phong_angle, flags.phong_angle_concave, flags.phong_group, flags.minlight, flags.minlight_color, flags.light_alpha, flags.maxlight, flags.lightcolorscale, flags.surflight_group, flags.world_units_per_luxel, flags.object_channel_mask); } diff --git a/include/common/bspfile.hh b/include/common/bspfile.hh index c008360b..df4f3d92 100644 --- a/include/common/bspfile.hh +++ b/include/common/bspfile.hh @@ -192,6 +192,9 @@ struct surfflags_t // normal if false, use a more natural angle falloff of 0% at 90 degrees bool surflight_rescale = true; + // override surface lighting style + std::optional surflight_style; + // override the textures' surflight color std::optional surflight_color; diff --git a/include/light/light.hh b/include/light/light.hh index eaacf2ae..6d84402a 100644 --- a/include/light/light.hh +++ b/include/light/light.hh @@ -305,6 +305,7 @@ public: setting_scalar surflightskyscale; // "choplight" - arghrad3 name setting_scalar surflightsubdivision; + setting_scalar surflight_minlight_scale; /* sunlight */ /* sun_light, sun_color, sun_angle for http://www.bspquakeeditor.com/arghrad/ compatibility */ diff --git a/include/qbsp/map.hh b/include/qbsp/map.hh index f909cc27..1c15c6de 100644 --- a/include/qbsp/map.hh +++ b/include/qbsp/map.hh @@ -250,6 +250,9 @@ struct mapdata_t // whether we had attempted loading texture stuff bool textures_loaded = false; + // map compile region + std::optional region = std::nullopt; + // helpers const std::string &miptexTextureName(int mt) const; const std::string &texinfoTextureName(int texinfo) const; @@ -284,6 +287,10 @@ struct texture_def_issues_t : logging::stat_tracker_t stat &num_translucent = register_stat( "faces have TRANSLUCENT flag swapped to DETAIL; TRANSLUCENT is an internal flag. Use -verbose to display affected faces.", false, true); + + stat &num_repaired = register_stat( + "faces have invalid texture projections and were repaired. Use -verbose to display affected faces." + , false, true); }; bool ParseEntity(parser_t &parser, mapentity_t &entity, texture_def_issues_t &issues_stats); diff --git a/light/entities.cc b/light/entities.cc index 209670bf..e66236ef 100644 --- a/light/entities.cc +++ b/light/entities.cc @@ -112,7 +112,7 @@ light_t::light_t() suntexture{this, "suntexture", ""}, nostaticlight{this, "nostaticlight", false}, surflight_group{this, "surflight_group", 0}, - surflight_minlight_scale{this, "surflight_minlight_scale", 64.f}, + surflight_minlight_scale{this, "surflight_minlight_scale", 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} @@ -339,9 +339,12 @@ static void CheckEntityFields(const mbsp_t *bsp, const settings::worldspawn_keys } if (!entity->surflight_minlight_scale.is_changed()) { - if (bsp->loadversion->game->id != GAME_QUAKE_II) { - // TODO: also use 1.0 for Q2? - entity->surflight_minlight_scale.set_value(1.0f, settings::source::DEFAULT); + if (cfg.surflight_minlight_scale.is_changed()) { + entity->surflight_minlight_scale.set_value(cfg.surflight_minlight_scale.value(), settings::source::DEFAULT); + } else if (bsp->loadversion->game->id == GAME_QUAKE_II) { + // this default value mimicks the fullbright-ish nature of emissive surfaces + // in Q2. + entity->surflight_minlight_scale.set_value(64.0f, settings::source::DEFAULT); } } } diff --git a/light/light.cc b/light/light.cc index df60cca7..e5ac6918 100644 --- a/light/light.cc +++ b/light/light.cc @@ -187,6 +187,7 @@ worldspawn_keys::worldspawn_keys() surflightscale{this, "surflightscale", 1.0, &worldspawn_group}, surflightskyscale{this, "surflightskyscale", 1.0, &worldspawn_group}, surflightsubdivision{this, {"surflightsubdivision", "choplight"}, 16.0, 1.0, 8192.0, &worldspawn_group}, + surflight_minlight_scale{this, "surflight_minlight_scale", 1.0f, 0.f, 510.f, &worldspawn_group }, sunlight{this, {"sunlight", "sun_light"}, 0.0, &worldspawn_group}, sunlight_color{this, {"sunlight_color", "sun_color"}, 255.0, 255.0, 255.0, &worldspawn_group}, sun2{this, "sun2", 0.0, &worldspawn_group}, @@ -1119,6 +1120,9 @@ static void LoadExtendedTexinfoFlags(const fs::path &sourcefilename, const mbsp_ if (val.contains("surflight_rescale")) { flags.surflight_rescale = val.at("surflight_rescale").get(); } + if (val.contains("surflight_style")) { + flags.surflight_style = val.at("surflight_style").get(); + } if (val.contains("surflight_color")) { flags.surflight_color = val.at("surflight_color").get(); } diff --git a/light/surflight.cc b/light/surflight.cc index 43e3308f..259524ea 100644 --- a/light/surflight.cc +++ b/light/surflight.cc @@ -138,7 +138,11 @@ static void MakeSurfaceLight(const mbsp_t *bsp, const settings::worldspawn_keys l.surfnormal = facenormal; l.omnidirectional = !is_directional; l.points = std::move(points); - l.style = style; + if (extended_flags.surflight_style) { + l.style = extended_flags.surflight_style.value(); + } else { + l.style = style; + } l.rescale = extended_flags.surflight_rescale; l.minlight_scale = extended_flags.surflight_minlight_scale; diff --git a/qbsp/brush.cc b/qbsp/brush.cc index 7ed7d18d..f068312d 100644 --- a/qbsp/brush.cc +++ b/qbsp/brush.cc @@ -712,6 +712,15 @@ static void Brush_LoadEntity(mapentity_t &dst, mapentity_t &src, hull_index_t hu for (auto &mapbrush : src.mapbrushes) { clock(); + if (map.region && (map.is_world_entity(src) || IsWorldBrushEntity(src) || IsNonRemoveWorldBrushEntity(src))) { + if (map.region->bounds.disjoint(mapbrush.bounds)) { + //stats.regioned_brushes++; + //it = entity.mapbrushes.erase(it); + //logging::print("removed broosh\n"); + continue; + } + } + contentflags_t contents = mapbrush.contents; if (qbsp_options.nodetail.value()) { diff --git a/qbsp/map.cc b/qbsp/map.cc index 43d2cf45..a4652bfc 100644 --- a/qbsp/map.cc +++ b/qbsp/map.cc @@ -340,6 +340,51 @@ struct texdef_etp_t bool tx2 = false; }; +/* +================ +CalculateBrushBounds +================ +*/ +inline void CalculateBrushBounds(mapbrush_t &ob) +{ + ob.bounds = {}; + + for (size_t i = 0; i < ob.faces.size(); i++) { + const auto &plane = ob.faces[i].get_plane(); + std::optional w = BaseWindingForPlane(plane); + + for (size_t j = 0; j < ob.faces.size() && w; j++) { + if (i == j) { + continue; + } + if (ob.faces[j].bevel) { + continue; + } + const auto &plane = map.get_plane(ob.faces[j].planenum ^ 1); + w = w->clip_front(plane, 0); // CLIP_EPSILON); + } + + if (w) { + // calc bounds before moving from w + for (auto &p : w.value()) { + ob.bounds += p; + } + ob.faces[i].winding = std::move(w.value()); + } + } + + for (size_t i = 0; i < 3; i++) { + if (ob.bounds.mins()[0] <= -qbsp_options.worldextent.value() || + ob.bounds.maxs()[0] >= qbsp_options.worldextent.value()) { + logging::print("WARNING: {}: brush bounds out of range\n", ob.line); + } + if (ob.bounds.mins()[0] >= qbsp_options.worldextent.value() || + ob.bounds.maxs()[0] <= -qbsp_options.worldextent.value()) { + logging::print("WARNING: {}: no visible sides on brush\n", ob.line); + } + } +} + using texdef_brush_primitives_t = qmat; static texdef_valve_t TexDef_BSPToValve(const texvecf &in_vecs); @@ -623,11 +668,21 @@ static surfflags_t SurfFlagsForEntity( qvec3d color; // FIXME: get_color, to match settings if (entity.epairs.has("_surflight_color") && entity.epairs.get_vector("_surflight_color", color) == 3) { - flags.surflight_color = qvec3b{ (uint8_t) (color[0] * 255), (uint8_t) (color[1] * 255), (uint8_t) (color[2] * 255) }; + if (color[0] <= 1 && color[1] <= 1 && color[2] <= 1) { + flags.surflight_color = qvec3b{ (uint8_t) (color[0] * 255), (uint8_t) (color[1] * 255), (uint8_t) (color[2] * 255) }; + } else { + flags.surflight_color = qvec3b{ (uint8_t) (color[0]), (uint8_t) (color[1]), (uint8_t) (color[2]) }; + } } } + if (entity.epairs.has("_surflight_style") && entity.epairs.get_int("_surflight_style") != 0) + flags.surflight_style = entity.epairs.get_int("_surflight_style"); + if (entity.epairs.has("_surflight_minlight_scale")) flags.surflight_minlight_scale = entity.epairs.get_float("_surflight_minlight_scale"); + // Paril: inherit _surflight_minlight_scale from worldspawn if unset + else if (!entity.epairs.has("_surflight_minlight_scale") && map.world_entity().epairs.has("_surflight_minlight_scale")) + flags.surflight_minlight_scale = map.world_entity().epairs.get_float("_surflight_minlight_scale"); // "_minlight_exclude", "_minlight_exclude2", "_minlight_exclude3"... for (int i = 0; i <= 9; i++) { @@ -1863,11 +1918,15 @@ inline bool IsValidTextureProjection(const mapface_t &mapface, const maptexinfo_ return IsValidTextureProjection(mapface.get_plane().get_normal(), tx->vecs.row(0).xyz(), tx->vecs.row(1).xyz()); } -static void ValidateTextureProjection(mapface_t &mapface, maptexinfo_t *tx) +static void ValidateTextureProjection(mapface_t &mapface, maptexinfo_t *tx, texture_def_issues_t &issue_stats) { if (!IsValidTextureProjection(mapface, tx)) { - logging::print("WARNING: {}: repairing invalid texture projection (\"{}\" near {} {} {})\n", mapface.line, - mapface.texname, (int)mapface.planepts[0][0], (int)mapface.planepts[0][1], (int)mapface.planepts[0][2]); + if (qbsp_options.verbose.value()) { + logging::print("WARNING: {}: repairing invalid texture projection (\"{}\" near {} {} {})\n", mapface.line, + mapface.texname, (int)mapface.planepts[0][0], (int)mapface.planepts[0][1], (int)mapface.planepts[0][2]); + } else { + issue_stats.num_repaired++; + } // Reset texturing to sensible defaults const std::array shift{0, 0}; @@ -1912,7 +1971,7 @@ static std::optional ParseBrushFace( } } - ValidateTextureProjection(face, &tx); + ValidateTextureProjection(face, &tx, issue_stats); tx.flags = SurfFlagsForEntity(tx, entity, face.contents); face.texinfo = FindTexinfo(tx); @@ -2459,6 +2518,74 @@ static mapbrush_t ParseBrush(parser_t &parser, mapentity_t &entity, texture_def_ brush.faces.emplace_back(std::move(face.value())); } + // check for region brush + if (string_iequals(brush.faces[0].texname, "region")) { + if (!map.is_world_entity(entity)) { + FError("Region brush at {} isn't part of the world entity", parser.token); + } + + CalculateBrushBounds(brush); + + // construct region brushes + for (auto &new_brush_side : brush.faces) { + + // copy the brush + mapbrush_t new_brush; + + new_brush.contents = brush.contents; + new_brush.line = brush.line; + + for (auto &side : brush.faces) { + + // if it's the side we're extruding, increase its dist + if (side.planenum == new_brush_side.planenum) { + mapface_t new_side; + new_side.texinfo = side.texinfo; + new_side.contents = side.contents; + new_side.raw_info = side.raw_info; + new_side.texname = side.texname; + new_side.planenum = side.planenum; + new_side.planenum = map.add_or_find_plane({ new_side.get_plane().get_normal(), new_side.get_plane().get_dist() + 16.f }); + + new_brush.faces.emplace_back(std::move(new_side)); + // the inverted side is special + } else if (side.get_plane().get_normal() == -new_brush_side.get_plane().get_normal()) { + + // add the other side + mapface_t flipped_side; + flipped_side.texinfo = side.texinfo; + flipped_side.contents = side.contents; + flipped_side.raw_info = side.raw_info; + flipped_side.texname = side.texname; + flipped_side.planenum = map.add_or_find_plane({ -new_brush_side.get_plane().get_normal(), -new_brush_side.get_plane().get_dist() }); + + new_brush.faces.emplace_back(std::move(flipped_side)); + } else { + mapface_t new_side; + new_side.texinfo = side.texinfo; + new_side.contents = side.contents; + new_side.raw_info = side.raw_info; + new_side.texname = side.texname; + new_side.planenum = side.planenum; + + new_brush.faces.emplace_back(std::move(new_side)); + } + } + + // add + new_brush.contents = Brush_GetContents(entity, new_brush); + map.world_entity().mapbrushes.push_back(std::move(new_brush)); + } + + if (!map.region) { + map.region = std::move(brush); + } else { + FError("Multiple region brushes detected; newest at {}", parser.token); + } + + return brush; + } + // mark hintskip faces if (is_hint) { int32_t num_hintskip = 0; @@ -2532,7 +2659,11 @@ bool ParseEntity(parser_t &parser, mapentity_t &entity, texture_def_issues_t &is } } while (parser.token != "}"); } else { - entity.mapbrushes.emplace_back(ParseBrush(parser, entity, issue_stats)); + auto brush = ParseBrush(parser, entity, issue_stats); + + if (brush.faces.size()) { + entity.mapbrushes.push_back(std::move(brush)); + } } } else { ParseEpair(parser, entity); @@ -2818,51 +2949,6 @@ bool IsNonRemoveWorldBrushEntity(const mapentity_t &entity) return false; } -/* -================ -CalculateBrushBounds -================ -*/ -inline void CalculateBrushBounds(mapbrush_t &ob) -{ - ob.bounds = {}; - - for (size_t i = 0; i < ob.faces.size(); i++) { - const auto &plane = ob.faces[i].get_plane(); - std::optional w = BaseWindingForPlane(plane); - - for (size_t j = 0; j < ob.faces.size() && w; j++) { - if (i == j) { - continue; - } - if (ob.faces[j].bevel) { - continue; - } - const auto &plane = map.get_plane(ob.faces[j].planenum ^ 1); - w = w->clip_front(plane, 0); // CLIP_EPSILON); - } - - if (w) { - // calc bounds before moving from w - for (auto &p : w.value()) { - ob.bounds += p; - } - ob.faces[i].winding = std::move(w.value()); - } - } - - for (size_t i = 0; i < 3; i++) { - if (ob.bounds.mins()[0] <= -qbsp_options.worldextent.value() || - ob.bounds.maxs()[0] >= qbsp_options.worldextent.value()) { - logging::print("WARNING: {}: brush bounds out of range\n", ob.line); - } - if (ob.bounds.mins()[0] >= qbsp_options.worldextent.value() || - ob.bounds.maxs()[0] <= -qbsp_options.worldextent.value()) { - logging::print("WARNING: {}: no visible sides on brush\n", ob.line); - } - } -} - inline bool MapBrush_IsHint(const mapbrush_t &brush) { for (auto &f : brush.faces) { @@ -2941,6 +3027,11 @@ void ProcessMapBrushes() map.total_brushes = 0; + if (map.region) { + CalculateBrushBounds(map.region.value()); + logging::print("NOTE: map region detected! only compiling map within {}\n", map.region.value().bounds); + } + { logging::percent_clock clock(map.entities.size()); @@ -3082,6 +3173,23 @@ void ProcessMapBrushes() logging::print(logging::flag::STAT, "\n"); + // remove ents in region + if (map.region) { + + for (auto it = map.entities.begin(); it != map.entities.end(); ) { + auto &entity = *it; + + if (!entity.mapbrushes.size()) { + if (map.region && !map.region->bounds.containsPoint(entity.origin)) { + it = map.entities.erase(it); + continue; + } + } + + ++it; + } + } + if (qbsp_options.debugexpand.is_changed()) { aabb3d hull; diff --git a/qbsp/writebsp.cc b/qbsp/writebsp.cc index 5335771e..305d1c79 100644 --- a/qbsp/writebsp.cc +++ b/qbsp/writebsp.cc @@ -384,6 +384,9 @@ static void WriteExtendedTexinfoFlags(void) if (tx.flags.surflight_rescale == false) { t["surflight_rescale"] = tx.flags.surflight_rescale; } + if (tx.flags.surflight_style.has_value()) { + t["surflight_style"] = tx.flags.surflight_style.value(); + } if (tx.flags.surflight_color.has_value()) { t["surflight_color"] = tx.flags.surflight_color.value(); }