diff --git a/include/qbsp/map.hh b/include/qbsp/map.hh index 5b9d2d2f..15ca2d0f 100644 --- a/include/qbsp/map.hh +++ b/include/qbsp/map.hh @@ -48,29 +48,61 @@ struct map_source_location // to be. note that because the locations only live for the lifetime // of the object it is belonging to, whatever this string // points to must out-live the object. - std::string_view source_name; + std::shared_ptr source_name = nullptr; // the line number that this location is associated to, if any. Synthetic // locations may not necessarily have an associated line number. - std::optional line_number; + std::optional line_number = std::nullopt; // reference to a location of the object that derived us. this is mainly // for synthetic locations; ie a bspbrush_t's sides aren't themselves generated // by a source or line, but they are derived from a mapbrush_t which does have - // a location. The object it points to must outlive this object. - const std::optional> derivative; + // a location. The object it points to must outlive this object. this is mainly + // for debugging. + std::optional> derivative = std::nullopt; + + explicit operator bool() const { return source_name != nullptr; } + + // return a modified source location with only the line changeed + inline map_source_location on_line(size_t new_line) const + { + return { source_name, new_line, derivative }; + } // if we update to C++20 we could use this to track where location objects come from: // std::source_location created_location; }; +// FMT support +template<> +struct fmt::formatter +{ + constexpr auto parse(format_parse_context &ctx) -> decltype(ctx.begin()) { return ctx.end(); } + + template + auto format(const map_source_location &v, FormatContext &ctx) -> decltype(ctx.out()) + { + if (v.source_name) { + format_to(ctx.out(), "{}", *v.source_name.get()); + } else { + format_to(ctx.out(), "unknown/unset location"); + } + + if (v.line_number.has_value()) { + format_to(ctx.out(), "[line {}]", v.line_number.value()); + } + + return ctx.out(); + } +}; + struct mapface_t { size_t planenum; std::array planepts{}; std::string texname{}; int texinfo = 0; - int linenum = 0; + map_source_location line; bool bevel = false; bool visible = false; winding_t winding; // winding used to calculate bevels @@ -105,7 +137,7 @@ public: brushformat_t format = brushformat_t::NORMAL; aabb3d bounds {}; std::optional outputnumber; /* only set for original brushes */ - size_t entitynum = 0, linenum = 0; + map_source_location line; }; struct lumpdata @@ -370,7 +402,7 @@ extern mapdata_t map; void CalculateWorldExtent(void); -bool ParseEntity(parser_t &parser, mapentity_t *entity); +bool ParseEntity(parser_t &parser, mapentity_t *entity, const map_source_location &map_source); void ProcessExternalMapEntity(mapentity_t *entity); void ProcessAreaPortal(mapentity_t *entity); diff --git a/qbsp/brush.cc b/qbsp/brush.cc index 35aa38c3..12a61ad2 100644 --- a/qbsp/brush.cc +++ b/qbsp/brush.cc @@ -80,11 +80,11 @@ static void CheckFace(side_t *face, const mapface_t &sourceface) if (face->w.size() < 3) { if (face->w.size() == 2) { logging::print( - "WARNING: line {}: partially clipped into degenerate polygon @ ({}) - ({})\n", sourceface.linenum, face->w[0], face->w[1]); + "WARNING: {}: partially clipped into degenerate polygon @ ({}) - ({})\n", sourceface.line, face->w[0], face->w[1]); } else if (face->w.size() == 1) { - logging::print("WARNING: line {}: partially clipped into degenerate polygon @ ({})\n", sourceface.linenum, face->w[0]); + logging::print("WARNING: {}: partially clipped into degenerate polygon @ ({})\n", sourceface.line, face->w[0]); } else { - logging::print("WARNING: line {}: completely clipped away\n", sourceface.linenum); + logging::print("WARNING: {}: completely clipped away\n", sourceface.line); } face->w.clear(); @@ -101,7 +101,7 @@ static void CheckFace(side_t *face, const mapface_t &sourceface) for (auto &v : p1) { if (fabs(v) > qbsp_options.worldextent.value()) { // this is fatal because a point should never lay outside the world - FError("line {}: coordinate out of range ({})\n", sourceface.linenum, v); + FError("{}: coordinate out of range ({})\n", sourceface.line, v); } } @@ -109,7 +109,7 @@ static void CheckFace(side_t *face, const mapface_t &sourceface) { vec_t dist = face->get_plane().distance_to(p1); if (fabs(dist) > qbsp_options.epsilon.value()) { - logging::print("WARNING: Line {}: Point ({:.3} {:.3} {:.3}) off plane by {:2.4}\n", sourceface.linenum, + logging::print("WARNING: {}: Point ({:.3} {:.3} {:.3}) off plane by {:2.4}\n", sourceface.line, p1[0], p1[1], p1[2], dist); } } @@ -118,8 +118,8 @@ static void CheckFace(side_t *face, const mapface_t &sourceface) qvec3d edgevec = p2 - p1; vec_t length = qv::length(edgevec); if (length < qbsp_options.epsilon.value()) { - logging::print("WARNING: Line {}: Healing degenerate edge ({}) at ({:.3f} {:.3} {:.3})\n", - sourceface.linenum, length, p1[0], p1[1], p1[2]); + logging::print("WARNING: {}: Healing degenerate edge ({}) at ({:.3f} {:.3} {:.3})\n", + sourceface.line, length, p1[0], p1[1], p1[2]); for (size_t j = i + 1; j < face->w.size(); j++) face->w[j - 1] = face->w[j]; face->w.resize(face->w.size() - 1); @@ -137,8 +137,8 @@ static void CheckFace(side_t *face, const mapface_t &sourceface) continue; vec_t dist = qv::dot(face->w[j], edgenormal); if (dist > edgedist) { - logging::print("WARNING: line {}: Found a non-convex face (error size {}, point: {})\n", - sourceface.linenum, dist - edgedist, face->w[j]); + logging::print("WARNING: {}: Found a non-convex face (error size {}, point: {})\n", + sourceface.line, dist - edgedist, face->w[j]); face->w.clear(); return; } @@ -252,9 +252,8 @@ contentflags_t Brush_GetContents(const mapbrush_t *mapbrush) } if (!contents.types_equal(base_contents, qbsp_options.target_game)) { - logging::print("mixed face contents ({} != {}) at line {}\n", - base_contents.to_string(qbsp_options.target_game), contents.to_string(qbsp_options.target_game), - mapface.linenum); + logging::print("WARNING: {}: mixed face contents ({} != {})\n", + mapface.line, base_contents.to_string(qbsp_options.target_game), contents.to_string(qbsp_options.target_game)); break; } } @@ -629,11 +628,12 @@ void bspbrush_t::update_bounds() } for (size_t i = 0; i < 3; i++) { + // todo: map_source_location in bspbrush_t if (this->bounds.mins()[0] <= -qbsp_options.worldextent.value() || this->bounds.maxs()[0] >= qbsp_options.worldextent.value()) { - logging::print("WARNING: line {}: brush bounds out of range\n", mapbrush->linenum); + logging::print("WARNING: {}: brush bounds out of range\n", mapbrush ? mapbrush->line : map_source_location()); } if (this->bounds.mins()[0] >= qbsp_options.worldextent.value() || this->bounds.maxs()[0] <= -qbsp_options.worldextent.value()) { - logging::print("WARNING: line {}: no visible sides on brush\n", mapbrush->linenum); + logging::print("WARNING: {}: no visible sides on brush\n", mapbrush ? mapbrush->line : map_source_location()); } } diff --git a/qbsp/brushbsp.cc b/qbsp/brushbsp.cc index 1c45b20a..1573fa25 100644 --- a/qbsp/brushbsp.cc +++ b/qbsp/brushbsp.cc @@ -1072,10 +1072,12 @@ static std::unique_ptr BrushBSP(mapentity_t *entity, std::vectororiginal->mapbrush->linenum); + logging::print("WARNING: {}: microbrush\n", + b->original->mapbrush->line); } for (side_t &side : b->sides) { diff --git a/qbsp/map.cc b/qbsp/map.cc index 55d666ba..667aa393 100644 --- a/qbsp/map.cc +++ b/qbsp/map.cc @@ -1475,14 +1475,14 @@ static void ParseTextureDef(parser_t &parser, mapface_t &mapface, const mapbrush if (!(extinfo.info->flags.native & (Q2_SURF_TRANS33 | Q2_SURF_TRANS66))) { extinfo.info->contents.native |= Q2_CONTENTS_DETAIL; - logging::print("WARNING: face at line {}: swapped TRANSLUCENT for DETAIL\n", mapface.linenum); + logging::print("WARNING: {}: swapped TRANSLUCENT for DETAIL\n", mapface.line); } } // This fixes a bug in some old maps. if ((extinfo.info->flags.native & (Q2_SURF_SKY | Q2_SURF_NODRAW)) == (Q2_SURF_SKY | Q2_SURF_NODRAW)) { extinfo.info->flags.native &= ~Q2_SURF_NODRAW; - logging::print("WARNING: face at line {}: SKY | NODRAW mixed. Removing NODRAW.\n", mapface.linenum); + logging::print("WARNING: {}: SKY | NODRAW mixed. Removing NODRAW.\n", mapface.line); } } @@ -1497,7 +1497,7 @@ static void ParseTextureDef(parser_t &parser, mapface_t &mapface, const mapbrush if (!contents.is_valid(qbsp_options.target_game, false)) { auto old_contents = contents; qbsp_options.target_game->contents_make_valid(contents); - logging::print("WARNING: line {}: face has invalid contents {}, remapped to {}\n", mapface.linenum, + logging::print("WARNING: {}: face has invalid contents {}, remapped to {}\n", mapface.line, old_contents.to_string(qbsp_options.target_game), contents.to_string(qbsp_options.target_game)); } @@ -1581,8 +1581,8 @@ inline bool IsValidTextureProjection(const mapface_t &mapface, const maptexinfo_ static void ValidateTextureProjection(mapface_t &mapface, maptexinfo_t *tx) { if (!IsValidTextureProjection(mapface, tx)) { - logging::print("WARNING: repairing invalid texture projection on line {} (\"{}\" near {} {} {})\n", - mapface.linenum, mapface.texname, (int)mapface.planepts[0][0], (int)mapface.planepts[0][1], + 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]); // Reset texturing to sensible defaults @@ -1603,7 +1603,8 @@ static std::optional ParseBrushFace(parser_t &parser, const mapbrush_ int i, j; mapface_t face; - face.linenum = parser.linenum; + face.line = brush.line.on_line(parser.linenum); + ParsePlaneDef(parser, planepts); normal_ok = face.set_planepts(planepts); @@ -1789,13 +1790,13 @@ inline void AddBrushBevels(mapentity_t &e, mapbrush_t &b) } } -static mapbrush_t ParseBrush(parser_t &parser, mapentity_t &entity) +static mapbrush_t ParseBrush(parser_t &parser, mapentity_t &entity, const map_source_location &entity_source) { mapbrush_t brush; // ericw -- brush primitives if (!parser.parse_token(PARSE_PEEK)) - FError("Unexpected EOF after { beginning brush"); + FError("{}: unexpected EOF after { beginning brush", entity_source); if (parser.token == "(") { brush.format = brushformat_t::NORMAL; @@ -1818,8 +1819,8 @@ static mapbrush_t ParseBrush(parser_t &parser, mapentity_t &entity) while (parser.parse_token()) { // set linenum after first parsed token - if (!brush.linenum) { - brush.linenum = parser.linenum; + if (!brush.line) { + brush.line = entity_source.on_line(parser.linenum); } if (parser.token == "}") @@ -1866,13 +1867,16 @@ static mapbrush_t ParseBrush(parser_t &parser, mapentity_t &entity) return brush; } -bool ParseEntity(parser_t &parser, mapentity_t *entity) +bool ParseEntity(parser_t &parser, mapentity_t *entity, const map_source_location &map_source) { if (!parser.parse_token()) return false; - if (parser.token != "{") - FError("line {}: Invalid entity format, { not found", parser.linenum); + map_source_location entity_source = map_source.on_line(parser.linenum); + + if (parser.token != "{") { + FError("{}: Invalid entity format, { not found", entity_source); + } entity->mapbrushes.clear(); @@ -1885,7 +1889,7 @@ bool ParseEntity(parser_t &parser, mapentity_t *entity) // once we run into the first brush, set up textures state. EnsureTexturesLoaded(); - entity->mapbrushes.emplace_back(ParseBrush(parser, *entity)); + entity->mapbrushes.emplace_back(ParseBrush(parser, *entity, entity_source)); } else { ParseEpair(parser, entity); } @@ -2007,9 +2011,10 @@ static mapentity_t LoadExternalMap(const std::string &filename) } parser_t parser(file->data(), file->size()); + map_source_location entity_source { std::make_shared(filename), parser.linenum }; // parse the worldspawn - if (!ParseEntity(parser, &dest)) { + if (!ParseEntity(parser, &dest, entity_source)) { FError("'{}': Couldn't parse worldspawn entity\n", filename); } const std::string &classname = dest.epairs.get("classname"); @@ -2019,7 +2024,7 @@ static mapentity_t LoadExternalMap(const std::string &filename) // parse any subsequent entities, move any brushes to worldspawn mapentity_t dummy{}; - while (ParseEntity(parser, &dummy)) { + while (ParseEntity(parser, &dummy, entity_source = entity_source.on_line(parser.linenum))) { // move the brushes to the worldspawn dest.mapbrushes.insert(dest.mapbrushes.end(), std::make_move_iterator(dummy.mapbrushes.begin()), std::make_move_iterator(dummy.mapbrushes.end())); @@ -2198,10 +2203,10 @@ inline void CalculateBrushBounds(mapbrush_t &ob) 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: line {}: brush bounds out of range\n", ob.linenum); + 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: line {}: no visible sides on brush\n", ob.linenum); + logging::print("WARNING: {}: no visible sides on brush\n", ob.line); } } } @@ -2239,7 +2244,8 @@ void ProcessMapBrushes() if (&entity == map.world_entity()) { logging::print("WARNING: Ignoring origin brush in worldspawn\n"); } else if (entity.epairs.has("origin")) { - logging::print("WARNING: Entity at line {} has multiple origin brushes\n", entity.mapbrushes.front().faces[0].linenum); + // fixme-brushbsp: entity.line + logging::print("WARNING: Entity at {} has multiple origin brushes\n", entity.mapbrushes.front().faces[0].line); } else { entity.origin = brush.bounds.centroid(); entity.epairs.set("origin", qv::to_string(entity.origin)); @@ -2350,11 +2356,12 @@ void LoadMapFile(void) } parser_t parser(file->data(), file->size()); + map_source_location entity_source { std::make_shared(qbsp_options.map_path.string()) }; for (int i = 0;; i++) { mapentity_t &entity = map.entities.emplace_back(); - if (!ParseEntity(parser, &entity)) { + if (!ParseEntity(parser, &entity, entity_source.on_line(parser.linenum))) { break; } } @@ -2375,11 +2382,12 @@ void LoadMapFile(void) } parser_t parser(file->data(), file->size()); + map_source_location entity_source { std::make_shared(qbsp_options.add.value()) }; for (int i = 0;; i++) { mapentity_t &entity = map.entities.emplace_back(); - if (!ParseEntity(parser, &entity)) { + if (!ParseEntity(parser, &entity, entity_source.on_line(parser.linenum))) { break; } diff --git a/tests/test_qbsp.cc b/tests/test_qbsp.cc index f9184313..70973fc4 100644 --- a/tests/test_qbsp.cc +++ b/tests/test_qbsp.cc @@ -44,10 +44,12 @@ static mapentity_t LoadMap(const char *map) // FIXME: ??? mapentity_t &entity = ::map.entities.emplace_back(); + map_source_location entity_source { std::make_shared(Catch::getResultCapture().getCurrentTestName()), 0 }; + mapentity_t worldspawn; // FIXME: adds the brush to the global map... - Q_assert(ParseEntity(parser, &worldspawn)); + Q_assert(ParseEntity(parser, &worldspawn, entity_source)); CalculateWorldExtent();