diff --git a/include/qbsp/map.hh b/include/qbsp/map.hh index 7473c4bf..2c46abc6 100644 --- a/include/qbsp/map.hh +++ b/include/qbsp/map.hh @@ -39,12 +39,12 @@ struct bspbrush_t; struct mapface_t { - //qbsp_plane_t plane{}; size_t planenum; std::array planepts{}; std::string texname{}; int texinfo = 0; int linenum = 0; + bool bevel = false; surfflags_t flags{}; @@ -55,6 +55,8 @@ struct mapface_t // for convert std::optional raw_info; + winding_t winding; + bool set_planepts(const std::array &pts); const texvecf &get_texvecs() const; @@ -76,6 +78,7 @@ public: int numfaces = 0; brushformat_t format = brushformat_t::NORMAL; int contents = 0; + aabb3d bounds {}; const mapface_t &face(int i) const; }; @@ -94,6 +97,8 @@ public: int firstmapbrush = 0; int nummapbrushes = 0; + size_t numboxbevels = 0; + size_t numedgebevels = 0; // key/value pairs in the order they were parsed entdict_t epairs; diff --git a/qbsp/brush.cc b/qbsp/brush.cc index ee3da8b4..4f737557 100644 --- a/qbsp/brush.cc +++ b/qbsp/brush.cc @@ -687,6 +687,12 @@ std::optional LoadBrush(const mapentity_t *src, const mapbrush_t *ma static void Brush_LoadEntity(mapentity_t *dst, const mapentity_t *src, const int hullnum, content_stats_base_t &stats) { + // _omitbrushes 1 just discards all brushes in the entity. + // could be useful for geometry guides, selective compilation, etc. + if (src->epairs.get_int("_omitbrushes")) { + return; + } + const mapbrush_t *mapbrush; qvec3d rotate_offset{}; int i; @@ -694,6 +700,7 @@ static void Brush_LoadEntity(mapentity_t *dst, const mapentity_t *src, const int bool all_detail, all_detail_fence, all_detail_illusionary; const std::string &classname = src->epairs.get("classname"); + /* Origin brush support */ rotation_t rottype = rotation_t::none; @@ -770,11 +777,6 @@ static void Brush_LoadEntity(mapentity_t *dst, const mapentity_t *src, const int const bool func_illusionary_visblocker = (0 == Q_strcasecmp(classname, "func_illusionary_visblocker")); - // _omitbrushes 1 just discards all brushes in the entity. - // could be useful for geometry guides, selective compilation, etc. - if (src->epairs.get_int("_omitbrushes")) - return; - for (i = 0; i < src->nummapbrushes; i++, mapbrush++) { logging::percent(i, src->nummapbrushes); mapbrush = &src->mapbrush(i); diff --git a/qbsp/faces.cc b/qbsp/faces.cc index a65d1058..6af0f199 100644 --- a/qbsp/faces.cc +++ b/qbsp/faces.cc @@ -235,7 +235,7 @@ static void EmitFaceFragment(face_t *face, face_fragment_t *fragment) GrowNodeRegion ============== */ -static void GrowNodeRegion(node_t *node) +static void EmitFaceFragments_R(node_t *node) { if (node->is_leaf) return; @@ -253,8 +253,8 @@ static void GrowNodeRegion(node_t *node) node->numfaces = static_cast(map.bsp.dfaces.size()) - node->firstface; - GrowNodeRegion(node->children[0].get()); - GrowNodeRegion(node->children[1].get()); + EmitFaceFragments_R(node->children[0].get()); + EmitFaceFragments_R(node->children[1].get()); } /* @@ -271,8 +271,8 @@ int MakeFaceEdges(node_t *headnode) firstface = static_cast(map.bsp.dfaces.size()); MakeFaceEdges_r(headnode); - logging::header("GrowRegions"); - GrowNodeRegion(headnode); + logging::header("EmitFaceFragments"); + EmitFaceFragments_R(headnode); return firstface; } diff --git a/qbsp/map.cc b/qbsp/map.cc index 4c09de52..6922b66d 100644 --- a/qbsp/map.cc +++ b/qbsp/map.cc @@ -421,11 +421,12 @@ int FindTexinfo(const maptexinfo_t &texinfo) return num_texinfo; } -static surfflags_t SurfFlagsForEntity(const maptexinfo_t &texinfo, const mapentity_t *entity) +static surfflags_t SurfFlagsForEntity(const maptexinfo_t &texinfo, const mapentity_t &entity) { surfflags_t flags{}; const char *texname = map.miptex.at(texinfo.miptex).name.c_str(); - const int shadow = entity->epairs.get_int("_shadow"); + const int shadow = entity.epairs.get_int("_shadow"); + // These flags are pulled from surf flags in Q2. // TODO: the Q1 version of this block can now be moved into texinfo // loading by shoving them inside of texinfo.flags like @@ -452,13 +453,13 @@ static surfflags_t SurfFlagsForEntity(const maptexinfo_t &texinfo, const mapenti } if (IsNoExpandName(texname)) flags.no_expand = true; - if (entity->epairs.get_int("_dirt") == -1) + if (entity.epairs.get_int("_dirt") == -1) flags.no_dirt = true; - if (entity->epairs.get_int("_bounce") == -1) + if (entity.epairs.get_int("_bounce") == -1) flags.no_bounce = true; - if (entity->epairs.get_int("_minlight") == -1) + if (entity.epairs.get_int("_minlight") == -1) flags.no_minlight = true; - if (entity->epairs.get_int("_lightignore") == 1) + if (entity.epairs.get_int("_lightignore") == 1) flags.light_ignore = true; // "_minlight_exclude", "_minlight_exclude2", "_minlight_exclude3"... @@ -468,7 +469,7 @@ static surfflags_t SurfFlagsForEntity(const maptexinfo_t &texinfo, const mapenti key += std::to_string(i); } - const std::string &excludeTex = entity->epairs.get(key.c_str()); + const std::string &excludeTex = entity.epairs.get(key.c_str()); if (!excludeTex.empty() && !Q_strcasecmp(texname, excludeTex)) { flags.no_minlight = true; } @@ -476,7 +477,7 @@ static surfflags_t SurfFlagsForEntity(const maptexinfo_t &texinfo, const mapenti if (shadow == -1) flags.no_shadow = true; - if (!Q_strcasecmp("func_detail_illusionary", entity->epairs.get("classname"))) { + if (!Q_strcasecmp("func_detail_illusionary", entity.epairs.get("classname"))) { /* Mark these entities as TEX_NOSHADOW unless the mapper set "_shadow" "1" */ if (shadow != 1) { flags.no_shadow = true; @@ -484,8 +485,8 @@ static surfflags_t SurfFlagsForEntity(const maptexinfo_t &texinfo, const mapenti } // handle "_phong" and "_phong_angle" and "_phong_angle_concave" - vec_t phongangle = entity->epairs.get_float("_phong_angle"); - const int phong = entity->epairs.get_int("_phong"); + vec_t phongangle = entity.epairs.get_float("_phong_angle"); + const int phong = entity.epairs.get_int("_phong"); if (phong && (phongangle == 0.0)) { phongangle = 89.0; // default _phong_angle @@ -495,11 +496,11 @@ static surfflags_t SurfFlagsForEntity(const maptexinfo_t &texinfo, const mapenti flags.phong_angle = clamp(phongangle, 0.0, 360.0); } - const vec_t phong_angle_concave = entity->epairs.get_float("_phong_angle_concave"); + const vec_t phong_angle_concave = entity.epairs.get_float("_phong_angle_concave"); flags.phong_angle_concave = clamp(phong_angle_concave, 0.0, 360.0); // handle "_minlight" - const vec_t minlight = entity->epairs.get_float("_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? @@ -510,9 +511,9 @@ static surfflags_t SurfFlagsForEntity(const maptexinfo_t &texinfo, const mapenti { qvec3d mincolor{}; - entity->epairs.get_vector("_mincolor", mincolor); + entity.epairs.get_vector("_mincolor", mincolor); if (qv::epsilonEmpty(mincolor, EQUAL_EPSILON)) { - entity->epairs.get_vector("_minlight_color", mincolor); + entity.epairs.get_vector("_minlight_color", mincolor); } mincolor = qv::normalize_color_format(mincolor); @@ -524,7 +525,7 @@ static surfflags_t SurfFlagsForEntity(const maptexinfo_t &texinfo, const mapenti } // handle "_light_alpha" - const vec_t lightalpha = entity->epairs.get_float("_light_alpha"); + const vec_t lightalpha = entity.epairs.get_float("_light_alpha"); if (lightalpha != 0.0) { flags.light_alpha = clamp(lightalpha, 0.0, 1.0); } @@ -1394,7 +1395,7 @@ parse_error: FError("line {}: couldn't parse Brush Primitives texture info", parser.linenum); } -static void ParseTextureDef(parser_t &parser, mapface_t &mapface, const mapbrush_t *brush, maptexinfo_t *tx, +static void ParseTextureDef(parser_t &parser, mapface_t &mapface, const mapbrush_t &brush, maptexinfo_t *tx, std::array &planepts, const qplane3d &plane) { vec_t rotate; @@ -1404,7 +1405,7 @@ static void ParseTextureDef(parser_t &parser, mapface_t &mapface, const mapbrush quark_tx_info_t extinfo; - if (brush->format == brushformat_t::BRUSH_PRIMITIVES) { + if (brush.format == brushformat_t::BRUSH_PRIMITIVES) { ParseBrushPrimTX(parser, texMat); tx_type = TX_BRUSHPRIM; @@ -1415,7 +1416,7 @@ static void ParseTextureDef(parser_t &parser, mapface_t &mapface, const mapbrush extinfo = ParseExtendedTX(parser); mapface.raw_info = extinfo.info; - } else if (brush->format == brushformat_t::NORMAL) { + } else if (brush.format == brushformat_t::NORMAL) { parser.parse_token(PARSE_SAMELINE); mapface.texname = parser.token; @@ -1608,24 +1609,24 @@ static void ValidateTextureProjection(mapface_t &mapface, maptexinfo_t *tx) } } -static std::unique_ptr ParseBrushFace(parser_t &parser, const mapbrush_t *brush, const mapentity_t *entity) +static std::optional ParseBrushFace(parser_t &parser, const mapbrush_t &brush, const mapentity_t &entity) { std::array planepts; bool normal_ok; maptexinfo_t tx; int i, j; - std::unique_ptr face = std::make_unique(); + mapface_t face; - face->linenum = parser.linenum; + face.linenum = parser.linenum; ParsePlaneDef(parser, planepts); - normal_ok = face->set_planepts(planepts); + normal_ok = face.set_planepts(planepts); - ParseTextureDef(parser, *face, brush, &tx, face->planepts, face->get_plane()); + ParseTextureDef(parser, face, brush, &tx, face.planepts, face.get_plane()); if (!normal_ok) { logging::print("WARNING: line {}: Brush plane with no normal\n", parser.linenum); - return nullptr; + return std::nullopt; } // ericw -- round texture vector values that are within ZERO_EPSILON of integers, @@ -1639,15 +1640,218 @@ static std::unique_ptr ParseBrushFace(parser_t &parser, const mapbrus } } - ValidateTextureProjection(*face, &tx); + ValidateTextureProjection(face, &tx); tx.flags = SurfFlagsForEntity(tx, entity); - face->texinfo = FindTexinfo(tx); + face.texinfo = FindTexinfo(tx); return face; } -mapbrush_t ParseBrush(parser_t &parser, const mapentity_t *entity) + +/* +================ +CalculateBrushBounds +================ +*/ +inline void CalculateBrushBounds(mapbrush_t &ob) +{ + ob.bounds = {}; + + for (size_t i = 0; i < ob.numfaces; i++) { + const auto &plane = ob.face(i).get_plane(); + std::optional w = BaseWindingForPlane(plane); + + for (size_t j = 0; j < ob.numfaces && w; j++) { + if (i == j) { + continue; + } + if (ob.face(j).bevel) { + continue; + } + const auto &plane = map.get_plane(ob.face(j).planenum ^ 1); + w = w->clip(plane, 0)[SIDE_FRONT]; //CLIP_EPSILON); + } + + if (w) { + const_cast(ob.face(i)).winding = w.value(); + //side->visible = true; + for (auto &p : w.value()) { + ob.bounds += p; + } + } + } + + 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: entity xxx, brush yyy: bounds out of range\n"); + } + if (ob.bounds.mins()[0] > qbsp_options.worldextent.value() || ob.bounds.maxs()[0] < -qbsp_options.worldextent.value()) { + logging::print("WARNING: entity xxx, brush yyy: no visible sides on brush\n"); + } + } +} + + +/* +================= +AddBrushBevels + +Adds any additional planes necessary to allow the brush to be expanded +against axial bounding boxes +================= +*/ +inline void AddBrushBevels(mapentity_t &e, mapbrush_t &b) +{ + // + // add the axial planes + // + int32_t order = 0; + for (int32_t axis = 0; axis < 3; axis++) { + for (int32_t dir = -1; dir <= 1; dir += 2, order++) { + // see if the plane is already present + int32_t i; + + for (i = 0; i < b.numfaces; i++) { + auto &s = b.face(i); + + if (map.get_plane(s.planenum).get_normal()[axis] == dir) { + break; + } + } + + if (i == b.numfaces) { + // add a new side + b.numfaces++; + mapface_t &s = map.faces.emplace_back(); + qplane3d plane{}; + plane.normal[axis] = dir; + if (dir == 1) { + plane.dist = b.bounds.maxs()[axis]; + } else { + plane.dist = -b.bounds.mins()[axis]; + } + s.planenum = map.add_or_find_plane(plane); + s.texinfo = b.face(0).texinfo; + s.contents = b.face(0).contents; + // fixme: why did we need to store all this stuff again, isn't + // it in texinfo? + s.raw_info = b.face(0).raw_info; + s.flags = b.face(0).flags; + s.texname = b.face(0).texname; + s.value = b.face(0).value; + + s.bevel = true; + e.numboxbevels++; + } + + // if the plane is not in it canonical order, swap it + if (i != order) { + std::swap(const_cast(b.face(order)), const_cast(b.face(i))); + } + } + } + + // + // add the edge bevels + // + if (b.numfaces == 6) { + return; // pure axial + } + + // test the non-axial plane edges + for (size_t i = 6; i < b.numfaces; i++) { + auto &s = b.face(i); + auto &w = s.winding; + + if (!w) { + continue; + } + + for (size_t j = 0; j < w.size(); j++) { + size_t k = (j + 1) % w.size(); + qvec3d vec = w[j] - w[k]; + + if (qv::normalizeInPlace(vec) < 0.5) { + continue; + } + + vec = qv::Snap(vec); + + for (k = 0 ; k < 3; k++) { + if (vec[k] == -1 || vec[k] == 1) { + break; // axial + } + } + + if (k != 3) { + continue; // only test non-axial edges + } + + // try the six possible slanted axials from this edge + for (size_t axis = 0; axis < 3; axis++) { + for (size_t dir = -1; dir <= 1; dir += 2) { + // construct a plane + qplane3d plane {}; + plane.normal[axis] = dir; + plane.normal = qv::cross(vec, plane.normal); + if (qv::normalizeInPlace(plane.normal) < 0.5) { + continue; + } + plane.dist = qv::dot(w[j], plane.normal); + + // if all the points on all the sides are + // behind this plane, it is a proper edge bevel + for (k = 0; k < b.numfaces; k++) { + // if this plane has allready been used, skip it + if (qv::epsilonEqual(b.face(k).get_plane(), plane)) { + break; + } + + auto &w2 = b.face(k).winding; + + if (!w2) { + continue; + } + + size_t l = 0; + for (; l < w2.size(); l++) { + vec_t d = qv::dot(w2[l], plane.normal) - plane.dist; + if (d > 0.1) { + break; // point in front + } + } + + if (l != w2.size()) { + break; + } + } + + if (k != b.numfaces) { + continue; // wasn't part of the outer hull + } + + // add this plane + b.numfaces++; + mapface_t &s = map.faces.emplace_back(); + s.planenum = map.add_or_find_plane(plane); + s.texinfo = b.face(0).texinfo; + s.contents = b.face(0).contents; + // fixme: why did we need to store all this stuff again, isn't + // it in texinfo? + s.raw_info = b.face(0).raw_info; + s.flags = b.face(0).flags; + s.texname = b.face(0).texname; + s.value = b.face(0).value; + s.bevel = true; + e.numedgebevels++; + } + } + } + } +} + +mapbrush_t ParseBrush(parser_t &parser, mapentity_t &entity) { mapbrush_t brush; @@ -1677,9 +1881,11 @@ mapbrush_t ParseBrush(parser_t &parser, const mapentity_t *entity) if (parser.token == "}") break; - std::unique_ptr face = ParseBrushFace(parser, &brush, entity); - if (face.get() == nullptr) + std::optional face = ParseBrushFace(parser, brush, entity); + + if (!face) { continue; + } /* Check for duplicate planes */ bool discardFace = false; @@ -1707,7 +1913,7 @@ mapbrush_t ParseBrush(parser_t &parser, const mapentity_t *entity) } brush.numfaces++; - map.faces.push_back(*face); + map.faces.emplace_back(std::move(face.value())); } // ericw -- brush primitives - there should be another closing } @@ -1719,6 +1925,12 @@ mapbrush_t ParseBrush(parser_t &parser, const mapentity_t *entity) } // ericw -- end brush primitives + // calculate brush bounds + CalculateBrushBounds(brush); + + // add the brush bevels + AddBrushBevels(entity, brush); + return brush; } @@ -1740,7 +1952,7 @@ bool ParseEntity(parser_t &parser, mapentity_t *entity) // once we run into the first brush, set up textures state. EnsureTexturesLoaded(); - mapbrush_t brush = ParseBrush(parser, entity); + mapbrush_t brush = ParseBrush(parser, *entity); if (!entity->nummapbrushes) entity->firstmapbrush = map.brushes.size(); diff --git a/qbsp/qbsp.cc b/qbsp/qbsp.cc index b134a94b..78b44aa5 100644 --- a/qbsp/qbsp.cc +++ b/qbsp/qbsp.cc @@ -317,135 +317,6 @@ static void ExportBrushList_r(const mapentity_t *entity, node_t *node) ExportBrushList_r(entity, node->children[1].get()); } -/* -================= -AddBrushBevels - -Adds any additional planes necessary to allow the brush to be expanded -against axial bounding boxes -================= -*/ -static std::vector> AddBrushBevels(const bspbrush_t &b) -{ - // add already-present planes - std::vector> planes; - - for (auto &f : b.sides) { - const qplane3d &plane = f.get_plane(); - int32_t outputplanenum = ExportMapPlane(plane); - planes.emplace_back(outputplanenum, &f); - } - - // - // add the axial planes - // - int32_t order = 0; - for (int32_t axis = 0; axis < 3; axis++) { - for (int32_t dir = -1; dir <= 1; dir += 2, order++) { - size_t i; - // see if the plane is allready present - for (i = 0; i < planes.size(); i++) { - if (map.bsp.dplanes[std::get<0>(planes[i])].normal[axis] == dir) - break; - } - - if (i == planes.size()) { - // add a new side - qplane3d new_plane{}; - new_plane.normal[axis] = dir; - if (dir == 1) - new_plane.dist = b.bounds.maxs()[axis]; - else - new_plane.dist = -b.bounds.mins()[axis]; - - int32_t outputplanenum = ExportMapPlane(new_plane); - planes.emplace_back(outputplanenum, &b.sides.front()); - } - - // if the plane is not in it canonical order, swap it - if (i != order) - std::swap(planes[i], planes[order]); - } - } - - // - // add the edge bevels - // - if (planes.size() == 6) - return planes; // pure axial - - // test the non-axial plane edges - size_t edges_to_test = planes.size(); - for (size_t i = 6; i < edges_to_test; i++) { - auto &s = std::get<1>(planes[i]); - if (!s) - continue; - auto &w = s->w; - if (!w.size()) - continue; - for (size_t j = 0; j < w.size(); j++) { - size_t k = (j + 1) % w.size(); - qvec3d vec = w[j] - w[k]; - if (qv::normalizeInPlace(vec) < 0.5) - continue; - vec = qv::Snap(vec); - for (k = 0; k < 3; k++) - if (vec[k] == -1 || vec[k] == 1) - break; // axial - if (k != 3) - continue; // only test non-axial edges - - // try the six possible slanted axials from this edge - for (int32_t axis = 0; axis < 3; axis++) { - for (int32_t dir = -1; dir <= 1; dir += 2) { - qvec3d vec2{}; - // construct a plane - vec2[axis] = dir; - qplane3d current; - current.normal = qv::cross(vec, vec2); - if (qv::normalizeInPlace(current.normal) < 0.5) - continue; - current.dist = qv::dot(w[j], current.normal); - - auto it = b.sides.begin(); - - // if all the points on all the sides are - // behind this plane, it is a proper edge bevel - for (; it != b.sides.end(); it++) { - auto &f = *it; - const qplane3d &plane = f.get_plane(); - - // if this plane has allready been used, skip it - if (qv::epsilonEqual(current, plane)) - break; - - auto &w2 = f.w; - if (!w2.size()) - continue; - size_t l; - for (l = 0; l < w2.size(); l++) { - vec_t d = current.distance_to(w2[l]); - if (d > 0.1) - break; // point in front - } - if (l != w2.size()) - break; - } - - if (it != b.sides.end()) - continue; // wasn't part of the outer hull - - // add this plane - int32_t outputplanenum = ExportMapPlane(current); - planes.emplace_back(outputplanenum, &b.sides.front()); - } - } - } - } - - return planes; -} - static void ExportBrushList(mapentity_t *entity, node_t *node) { logging::funcheader(); @@ -458,11 +329,9 @@ static void ExportBrushList(mapentity_t *entity, node_t *node) dbrush_t &brush = map.bsp.dbrushes.emplace_back( dbrush_t{static_cast(map.bsp.dbrushsides.size()), 0, b->contents.native}); - auto bevels = AddBrushBevels(*b); - - for (auto &plane : bevels) { + for (auto &side : b->sides) { map.bsp.dbrushsides.push_back( - {(uint32_t)std::get<0>(plane), (int32_t)ExportMapTexinfo(std::get<1>(plane)->texinfo)}); + {(uint32_t) side.planenum, (int32_t)ExportMapTexinfo(side.texinfo)}); brush.numsides++; brush_state.total_brush_sides++; }