Merge branch 'type-cleanup' into brushbsp

This commit is contained in:
Eric Wasylishen 2022-05-13 01:24:14 -06:00
commit 4957622c50
9 changed files with 94 additions and 46 deletions

View File

@ -318,6 +318,10 @@ static void serialize_bsp(const bspdata_t &bspdata, const mbsp_t &bsp, const fs:
node.push_back({"maxs", src_node.maxs});
node.push_back({"firstface", src_node.firstface});
node.push_back({"numfaces", src_node.numfaces});
// human-readable plane
auto& plane = bsp.dplanes.at(src_node.planenum);
node.push_back({"plane", json::array({plane.normal[0], plane.normal[1], plane.normal[2], plane.dist})});
}
}

View File

@ -770,6 +770,13 @@ struct bsp2_dclipnode_t
auto stream_data() { return std::tie(planenum, children); }
};
/*
* Clipnodes need to be stored as a 16-bit offset. Originally, this was a
* signed value and only the positive values up to 32767 were available. Since
* the negative range was unused apart from a few values reserved for flags,
* this has been extended to allow up to 65520 (0xfff0) clipnodes (with a
* suitably modified engine).
*/
struct bsp29_dclipnode_t
{
int32_t planenum;

View File

@ -150,8 +150,6 @@ extern setting_group debugging_group;
class qbsp_settings : public common_settings
{
public:
inline qbsp_settings() { }
setting_bool hexen2{this, "hexen2", false, &game_target_group, "target Hexen II's BSP format"};
setting_bool hlbsp{this, "hlbsp", false, &game_target_group, "target Half Life's BSP format"};
setting_bool q2bsp{this, "q2bsp", false, &game_target_group, "target Quake II's BSP format"};
@ -250,15 +248,6 @@ public:
extern settings::qbsp_settings options;
/*
* Clipnodes need to be stored as a 16-bit offset. Originally, this was a
* signed value and only the positive values up to 32767 were available. Since
* the negative range was unused apart from a few values reserved for flags,
* this has been extended to allow up to 65520 (0xfff0) clipnodes (with a
* suitably modified engine).
*/
#define MAX_BSP_CLIPNODES 0xfff0
// 0-2 are axial planes
// 3-5 are non-axial planes snapped to the nearest
#define PLANE_X 0
@ -268,7 +257,7 @@ extern settings::qbsp_settings options;
#define PLANE_ANYY 4
#define PLANE_ANYZ 5
// planenum for a leaf (?)
// planenum for a leaf
constexpr int32_t PLANENUM_LEAF = -1;
/*

View File

@ -119,5 +119,5 @@ public:
}
};
constexpr reference operator[](const size_t &index) { return {bits, index >> shift, 1u << (index & mask)}; }
constexpr reference operator[](const size_t &index) { return {bits, index >> shift, static_cast<size_t>(1) << (index & mask)}; }
};

View File

@ -64,6 +64,15 @@ if (embree_FOUND)
message(STATUS "Found embree license: ${EMBREE_LICENSE}")
endif()
# HACK: Windows embree .dll's from https://github.com/embree/embree/releases ship with a tbb12.dll
# and we need to copy it from the embree/bin directory to our light.exe/testlight.exe dir in order for them to run
find_file(EMBREE_TBB_DLL tbb12.dll
"${EMBREE_ROOT_DIR}/bin"
NO_DEFAULT_PATH)
if (NOT EMBREE_TBB_DLL STREQUAL EMBREE_TBB_DLL-NOTFOUND)
message(STATUS "Found embree EMBREE_TBB_DLL: ${EMBREE_TBB_DLL}")
endif()
add_custom_command(TARGET light POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different "$<TARGET_FILE:embree>" "$<TARGET_FILE_DIR:light>"
COMMAND ${CMAKE_COMMAND} -E copy_if_different "$<TARGET_FILE:TBB::tbb>" "$<TARGET_FILE_DIR:light>")
@ -72,6 +81,10 @@ if (embree_FOUND)
add_custom_command(TARGET light POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${EMBREE_LICENSE}" "$<TARGET_FILE_DIR:light>/LICENSE-embree.txt")
endif()
if (NOT EMBREE_TBB_DLL STREQUAL EMBREE_TBB_DLL-NOTFOUND)
add_custom_command(TARGET light POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${EMBREE_TBB_DLL}" "$<TARGET_FILE_DIR:light>")
endif()
# so the executable will search for dylib's in the same directory as the executable
if(APPLE)
@ -123,11 +136,9 @@ if (embree_FOUND)
COMMAND ${CMAKE_COMMAND} -E copy_if_different "$<TARGET_FILE:TBB::tbb>" "$<TARGET_FILE_DIR:testlight>"
COMMAND ${CMAKE_COMMAND} -E copy_if_different "$<TARGET_FILE:embree>" "$<TARGET_FILE_DIR:testlight>")
# HACK: copy embree's tbb12.dll
# FIXME: this is only desired with the .zip release of embree, not e.g. vcpkg
if (WIN32)
if (NOT EMBREE_TBB_DLL STREQUAL EMBREE_TBB_DLL-NOTFOUND)
add_custom_command(TARGET testlight POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different "$<TARGET_FILE_DIR:embree>/tbb12.dll" "$<TARGET_FILE_DIR:testlight>")
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${EMBREE_TBB_DLL}" "$<TARGET_FILE_DIR:testlight>")
endif()
add_definitions(-DHAVE_EMBREE)

View File

@ -2095,7 +2095,7 @@ void ConvertMapFile(void)
}
fs::path filename = options.bsp_path;
filename.replace_filename(options.bsp_path.stem().string() + append);
filename.replace_filename(options.bsp_path.stem().string() + append).replace_extension(".map");
std::ofstream f(filename);

View File

@ -109,7 +109,8 @@ void qbsp_settings::postinitialize(int argc, const char **argv)
set_target_version(&bspver_hl);
}
if (q2bsp.value() || q2rtx.value()) {
if (q2bsp.value() ||
(q2rtx.value() && !q2bsp.isChanged() && !qbism.isChanged())) {
set_target_version(&bspver_q2);
}
@ -167,10 +168,6 @@ void qbsp_settings::postinitialize(int argc, const char **argv)
if (!includeskip.isChanged()) {
includeskip.setValueLocked(true);
}
if (!notriggermodels.isChanged()) {
notriggermodels.setValueLocked(true);
}
}
common_settings::postinitialize(argc, argv);
@ -596,6 +593,23 @@ winding_t BaseWindingForPlane(const qplane3d &p)
return winding_t::from_plane(p, options.worldextent.value());
}
static bool IsTrigger(const mapentity_t *entity)
{
auto &tex = entity->mapbrush(0).face(0).texname;
if (tex.length() < 6) {
return false;
}
size_t trigger_pos = tex.rfind("trigger");
if (trigger_pos == std::string::npos) {
return false;
}
return trigger_pos == (tex.size() - strlen("trigger"));
}
/*
===============
ProcessEntity
@ -619,9 +633,9 @@ static void ProcessEntity(mapentity_t *entity, const int hullnum)
return;
// for notriggermodels: if we have at least one trigger-like texture, do special trigger stuff
bool discarded_trigger = (entity != map.world_entity() &&
bool discarded_trigger = entity != map.world_entity() &&
options.notriggermodels.value() &&
entity->mapbrush(0).face(0).texname.find_last_of("trigger") == entity->mapbrush(0).face(0).texname.size() - strlen("trigger"));
IsTrigger(entity);
// Export a blank model struct, and reserve the index (only do this once, for all hulls)
if (!discarded_trigger) {

View File

@ -30,6 +30,21 @@
#include <map>
#include <list>
static bool ShouldOmitFace(face_t *f)
{
if (!options.includeskip.value() && map.mtexinfos.at(f->texinfo).flags.is_skip)
return true;
if (map.mtexinfos.at(f->texinfo).flags.is_hint)
return true;
// HACK: to save a few faces, don't output the interior faces of sky brushes
if (f->contents[0].is_sky(options.target_game)) {
return true;
}
return false;
}
/*
===============
SubdivideFace
@ -312,9 +327,7 @@ FindFaceEdges
*/
static void FindFaceEdges(mapentity_t *entity, face_t *face)
{
if (!options.includeskip.value() && map.mtexinfos.at(face->texinfo).flags.is_skip)
return;
if (map.mtexinfos.at(face->texinfo).flags.is_hint)
if (ShouldOmitFace(face))
return;
FindFaceFragmentEdges(entity, face, face);
@ -387,9 +400,7 @@ EmitFace
*/
static void EmitFace(mapentity_t *entity, face_t *face)
{
if (!options.includeskip.value() && map.mtexinfos.at(face->texinfo).flags.is_skip)
return;
if (map.mtexinfos.at(face->texinfo).flags.is_hint)
if (ShouldOmitFace(face))
return;
EmitFaceFragment(entity, face, face);
@ -426,9 +437,7 @@ static void GrowNodeRegion(mapentity_t *entity, node_t *node)
static void CountFace(mapentity_t *entity, face_t *f, size_t &facesCount, size_t &vertexesCount)
{
if (!options.includeskip.value() && map.mtexinfos.at(f->texinfo).flags.is_skip)
return;
if (map.mtexinfos.at(f->texinfo).flags.is_hint)
if (ShouldOmitFace(f))
return;
if (f->lmshift[1] != 4)

View File

@ -16,6 +16,8 @@
#include <set>
#include <map>
using namespace testing;
// FIXME: Clear global data (planes, etc) between each test
static const mapface_t *Mapbrush_FirstFaceWithTextureName(const mapbrush_t *brush, const std::string &texname)
@ -492,15 +494,26 @@ TEST(testmaps_q1, simple_worldspawn_sky)
EXPECT_EQ(5, textureToFace.at("orangestuff8").size());
// leaf/node counts
EXPECT_EQ(6, bsp.dnodes.size());
// - we'd get 7 nodes if it's cut like a cube (solid outside), with 1 additional cut inside to divide sky / empty
// - we'd get 11 if it's cut as the sky plane (1), then two open cubes (5 nodes each)
// - can get in between values if it does some vertical cuts, then the sky plane, then other vertical cuts
//
// the 7 solution is better but the BSP heuristics won't help reach that one in this trivial test map
EXPECT_THAT(bsp.dnodes.size(), AllOf(Ge(7), Le(11)));
EXPECT_EQ(3, bsp.dleafs.size()); // shared solid leaf + empty + sky
// check contents
const qvec3d player_pos{-88, -64, 120};
const double inside_sky_z = 232;
EXPECT_EQ(CONTENTS_EMPTY, BSP_FindLeafAtPoint(&bsp, &bsp.dmodels[0], player_pos)->contents);
EXPECT_EQ(CONTENTS_SKY, BSP_FindLeafAtPoint(&bsp, &bsp.dmodels[0], player_pos + qvec3d(0,0,500))->contents);
// way above map is solid - sky should not fill outwards
// (otherwise, if you had sky with a floor further up above it, it's not clear where the leafs would be divided, or
// if the floor contents would turn to sky, etc.)
EXPECT_EQ(CONTENTS_SOLID, BSP_FindLeafAtPoint(&bsp, &bsp.dmodels[0], player_pos + qvec3d(0,0,500))->contents);
EXPECT_EQ(CONTENTS_SKY, BSP_FindLeafAtPoint(&bsp, &bsp.dmodels[0], qvec3d(player_pos[0], player_pos[1], inside_sky_z))->contents);
EXPECT_EQ(CONTENTS_SOLID, BSP_FindLeafAtPoint(&bsp, &bsp.dmodels[0], player_pos + qvec3d( 500, 0, 0))->contents);
EXPECT_EQ(CONTENTS_SOLID, BSP_FindLeafAtPoint(&bsp, &bsp.dmodels[0], player_pos + qvec3d(-500, 0, 0))->contents);
@ -752,30 +765,31 @@ TEST(testmaps_q2, detail) {
// stats
EXPECT_EQ(1, bsp.dmodels.size());
// Q2 reserves leaf 0 as an invalid leaf
// leafs:
// 6 solid leafs outside the room
// 6 solid leafs outside the room (* can be more depending on when the "divider" is cut)
// 1 empty leaf filling the room above the divider
// 2 empty leafs + 1 solid leaf for divider
// 1 detail leaf for button
// 4 empty leafs around + 1 on top of button
// total: 16
// Q2 reserves leaf 0 as an invalid leaf, so dleafs size is 17
EXPECT_EQ(17, bsp.dleafs.size());
std::map<int32_t, int> counts_by_contents;
for (size_t i = 1; i < bsp.dleafs.size(); ++i) {
++counts_by_contents[bsp.dleafs[i].contents];
}
EXPECT_EQ(3, counts_by_contents.size()); // number of types
EXPECT_EQ((std::map<int32_t, int>{{Q2_CONTENTS_SOLID, 7}, {Q2_CONTENTS_SOLID | Q2_CONTENTS_DETAIL, 1}, {0, 8}}),
counts_by_contents);
EXPECT_EQ(1, counts_by_contents.at(Q2_CONTENTS_SOLID | Q2_CONTENTS_DETAIL));
EXPECT_EQ(8, counts_by_contents.at(0)); // empty leafs
EXPECT_THAT(counts_by_contents.at(Q2_CONTENTS_SOLID), AllOf(Ge(7), Le(9)));
// clusters:
// 6 solid leafs outside the room
// 1 empty leaf filling the room above the divider
// 2 empty leafs + 1 solid leaf for divider
// 1 empty cluster filling the room above the divider
// 2 empty clusters created by divider
// 1 cluster for the part of the room with the button
// total: 10
std::set<int> clusters;
// first add the empty leafs
for (size_t i = 1; i < bsp.dleafs.size(); ++i) {