qbsp: shrink node_t from 280 to 208 bytes

- contentflags_t from 80 to 8 bytes
This commit is contained in:
Eric Wasylishen 2024-03-03 20:41:58 -07:00
parent 3d419853d3
commit aa3b8479b4
23 changed files with 808 additions and 780 deletions

View File

@ -495,7 +495,7 @@ static void FindLeaf(const mbsp_t *bsp, const qvec3d &pos)
const mleaf_t *leaf = BSP_FindLeafAtPoint(bsp, &bsp->dmodels[0], pos); const mleaf_t *leaf = BSP_FindLeafAtPoint(bsp, &bsp->dmodels[0], pos);
fmt::print("leaf {}: contents {} ({})\n", (leaf - bsp->dleafs.data()), leaf->contents, fmt::print("leaf {}: contents {} ({})\n", (leaf - bsp->dleafs.data()), leaf->contents,
contentflags_t{leaf->contents}.to_string(bsp->loadversion->game)); bsp->loadversion->game->create_contents_from_native(leaf->contents).to_string(bsp->loadversion->game));
} }
// map file stuff // map file stuff

File diff suppressed because it is too large Load Diff

View File

@ -186,7 +186,9 @@ struct compiled_brush_t
side.valve.shift[0], side.valve.axis.at(1, 0), side.valve.axis.at(1, 1), side.valve.axis.at(1, 2), side.valve.shift[0], side.valve.axis.at(1, 0), side.valve.axis.at(1, 1), side.valve.axis.at(1, 2),
side.valve.shift[1], 0.0, side.valve.scale[0], side.valve.scale[1]); side.valve.shift[1], 0.0, side.valve.scale[0], side.valve.scale[1]);
if (bsp->loadversion->game->id == GAME_QUAKE_II && (contents.native || side.flags.native || side.value)) { int native = bsp->loadversion->game->contents_to_native(contents);
if (bsp->loadversion->game->id == GAME_QUAKE_II && (native || side.flags.native || side.value)) {
wal_metadata_t *meta = nullptr; wal_metadata_t *meta = nullptr;
auto it = wals.find(side.texture_name); auto it = wals.find(side.texture_name);
@ -207,9 +209,9 @@ struct compiled_brush_t
} }
if (!meta || !((meta->contents & ~(Q2_CONTENTS_SOLID | Q2_CONTENTS_WINDOW)) == if (!meta || !((meta->contents & ~(Q2_CONTENTS_SOLID | Q2_CONTENTS_WINDOW)) ==
(contents.native & ~(Q2_CONTENTS_SOLID | Q2_CONTENTS_WINDOW)) && (native & ~(Q2_CONTENTS_SOLID | Q2_CONTENTS_WINDOW)) &&
meta->flags == side.flags.native && meta->value == side.value)) { meta->flags == side.flags.native && meta->value == side.value)) {
ewt::print(stream, " {} {} {}", contents.native, side.flags.native, side.value); ewt::print(stream, " {} {} {}", native, side.flags.native, side.value);
} }
} }
@ -549,8 +551,10 @@ static const char *DefaultOriginTexture(const mbsp_t *bsp)
static const char *DefaultTextureForContents(const mbsp_t *bsp, const contentflags_t &contents) static const char *DefaultTextureForContents(const mbsp_t *bsp, const contentflags_t &contents)
{ {
int native = bsp->loadversion->game->contents_to_native(contents);
if (bsp->loadversion->game->id == GAME_QUAKE_II) { if (bsp->loadversion->game->id == GAME_QUAKE_II) {
int visible = contents.native & Q2_ALL_VISIBLE_CONTENTS; int visible = native & Q2_ALL_VISIBLE_CONTENTS;
if (visible & Q2_CONTENTS_WATER) { if (visible & Q2_CONTENTS_WATER) {
return "e1u1/water4"; return "e1u1/water4";
@ -558,17 +562,17 @@ static const char *DefaultTextureForContents(const mbsp_t *bsp, const contentfla
return "e1u1/sewer1"; return "e1u1/sewer1";
} else if (visible & Q2_CONTENTS_LAVA) { } else if (visible & Q2_CONTENTS_LAVA) {
return "e1u1/brlava"; return "e1u1/brlava";
} else if (contents.native & Q2_CONTENTS_PLAYERCLIP) { } else if (native & Q2_CONTENTS_PLAYERCLIP) {
return "e1u1/clip"; return "e1u1/clip";
} else if (contents.native & Q2_CONTENTS_MONSTERCLIP) { } else if (native & Q2_CONTENTS_MONSTERCLIP) {
return "e1u1/clip_mon"; return "e1u1/clip_mon";
} else if (contents.native & Q2_CONTENTS_AREAPORTAL) { } else if (native & Q2_CONTENTS_AREAPORTAL) {
return "e1u1/trigger"; return "e1u1/trigger";
} }
return "e1u1/skip"; return "e1u1/skip";
} else { } else {
switch (contents.native) { switch (native) {
case CONTENTS_WATER: return "*waterskip"; case CONTENTS_WATER: return "*waterskip";
case CONTENTS_SLIME: return "*slimeskip"; case CONTENTS_SLIME: return "*slimeskip";
case CONTENTS_LAVA: return "*lavaskip"; case CONTENTS_LAVA: return "*lavaskip";
@ -585,9 +589,10 @@ static void OverrideTextureForContents(
compiled_brush_side_t &side, const mbsp_t *bsp, const char *name, const contentflags_t &contents) compiled_brush_side_t &side, const mbsp_t *bsp, const char *name, const contentflags_t &contents)
{ {
if (bsp->loadversion->game->id == GAME_QUAKE_II) { if (bsp->loadversion->game->id == GAME_QUAKE_II) {
int native = bsp->loadversion->game->contents_to_native(contents);
if (contents.native & (Q2_CONTENTS_PLAYERCLIP | Q2_CONTENTS_MONSTERCLIP)) { if (native & (Q2_CONTENTS_PLAYERCLIP | Q2_CONTENTS_MONSTERCLIP)) {
if (!(contents.native & Q2_CONTENTS_PLAYERCLIP)) { if (!(native & Q2_CONTENTS_PLAYERCLIP)) {
side.texture_name = "e1u1/clip_mon"; side.texture_name = "e1u1/clip_mon";
} else { } else {
side.texture_name = "e1u1/clip"; side.texture_name = "e1u1/clip";
@ -808,7 +813,9 @@ static std::vector<compiled_brush_t> DecompileLeafTaskGeometryOnly(
compiled_brush_t brush; compiled_brush_t brush;
brush.source = task.brush; brush.source = task.brush;
brush.brush_offset = brush_offset; brush.brush_offset = brush_offset;
brush.contents = {task.brush ? task.brush->contents : task.leaf ? task.leaf->contents : task.contents.value()}; brush.contents = bsp->loadversion->game->create_contents_from_native(
task.brush ? task.brush->contents : task.leaf ? task.leaf->contents : task.contents.value()
);
brush.sides.reserve(task.allPlanes.size()); brush.sides.reserve(task.allPlanes.size());
@ -868,7 +875,7 @@ static std::vector<compiled_brush_t> DecompileLeafTask(
compiled_brush_t brush; compiled_brush_t brush;
brush.source = task.brush; brush.source = task.brush;
brush.brush_offset = brush_offset; brush.brush_offset = brush_offset;
brush.contents = {task.brush ? task.brush->contents : task.leaf ? task.leaf->contents : task.contents.value()}; brush.contents = bsp->loadversion->game->create_contents_from_native(task.brush ? task.brush->contents : task.leaf ? task.leaf->contents : task.contents.value());
for (auto &finalSide : finalBrush.sides) { for (auto &finalSide : finalBrush.sides) {
compiled_brush_side_t &side = brush.sides.emplace_back(); compiled_brush_side_t &side = brush.sides.emplace_back();
@ -876,7 +883,7 @@ static std::vector<compiled_brush_t> DecompileLeafTask(
side.winding = std::move(finalSide.winding); side.winding = std::move(finalSide.winding);
side.source = finalSide.plane.source; side.source = finalSide.plane.source;
if (brush.contents.native == 0) { if (bsp->loadversion->game->contents_to_native(brush.contents) == 0) {
// hint brush // hint brush
side.texture_name = "e1u1/hint"; side.texture_name = "e1u1/hint";
@ -959,7 +966,9 @@ static std::vector<compiled_brush_t> DecompileLeafTaskLeafVisualization(
compiled_brush_t brush; compiled_brush_t brush;
brush.source = task.brush; brush.source = task.brush;
brush.brush_offset = brush_offset; brush.brush_offset = brush_offset;
brush.contents = task.leaf ? contentflags_t{task.leaf->contents} : contentflags_t{task.contents.value()}; brush.contents = bsp->loadversion->game->create_contents_from_native(
task.leaf ? task.leaf->contents : task.contents.value()
);
for (auto &finalSide : finalBrush.sides) { for (auto &finalSide : finalBrush.sides) {
compiled_brush_side_t &side = brush.sides.emplace_back(); compiled_brush_side_t &side = brush.sides.emplace_back();
@ -1324,7 +1333,7 @@ static void DecompileEntity(
std::vector<compiled_brush_t> &brushlist = compiledBrushes.emplace_back(); std::vector<compiled_brush_t> &brushlist = compiledBrushes.emplace_back();
compiled_brush_t &brush = brushlist.emplace_back(); compiled_brush_t &brush = brushlist.emplace_back();
brush.brush_offset = brush_offset; brush.brush_offset = brush_offset;
brush.contents = {Q2_CONTENTS_ORIGIN}; brush.contents = contentflags_t::make(EWT_INVISCONTENTS_ORIGIN);
constexpr qplane3d planes[] = { constexpr qplane3d planes[] = {
{{-1, 0, 0}, 8}, {{-1, 0, 0}, 8},

View File

@ -145,7 +145,7 @@ std::optional<texture> load_wal(
tex.meta.name = name; tex.meta.name = name;
tex.meta.width = tex.width = mt.width; tex.meta.width = tex.width = mt.width;
tex.meta.height = tex.height = mt.height; tex.meta.height = tex.height = mt.height;
tex.meta.contents = {mt.contents}; tex.meta.contents_native = mt.contents;
tex.meta.flags = {mt.flags}; tex.meta.flags = {mt.flags};
tex.meta.value = mt.value; tex.meta.value = mt.value;
tex.meta.animation = mt.animname.data(); tex.meta.animation = mt.animname.data();
@ -402,17 +402,20 @@ std::optional<texture_meta> load_wal_json_meta(
auto &contents = json["contents"]; auto &contents = json["contents"];
if (contents.is_number_integer()) { if (contents.is_number_integer()) {
meta.contents.native = contents.get<int32_t>(); meta.contents_native = contents.get<int32_t>();
} else if (contents.is_string()) { } else if (contents.is_string()) {
meta.contents.native = game->contents_from_string(contents.get<std::string>()); meta.contents_native =
game->contents_from_string(contents.get<std::string>());
} else if (contents.is_array()) { } else if (contents.is_array()) {
int native = 0;
for (auto &content : contents) { for (auto &content : contents) {
if (content.is_number_integer()) { if (content.is_number_integer()) {
meta.contents.native |= content.get<int32_t>(); native |= content.get<int32_t>();
} else if (content.is_string()) { } else if (content.is_string()) {
meta.contents.native |= game->contents_from_string(content.get<std::string>()); native |= game->contents_from_string(content.get<std::string>());
} }
} }
meta.contents_native = native;
} }
} }

View File

@ -191,7 +191,7 @@ void brush_side_t::parse_extended_texinfo(parser_t &parser)
if (parser.parse_token(PARSE_OPTIONAL)) { if (parser.parse_token(PARSE_OPTIONAL)) {
texinfo_quake2_t q2_info; texinfo_quake2_t q2_info;
q2_info.contents = {std::stoi(parser.token)}; q2_info.contents = std::stoi(parser.token);
if (parser.parse_token(PARSE_OPTIONAL)) { if (parser.parse_token(PARSE_OPTIONAL)) {
q2_info.flags.native = std::stoi(parser.token); q2_info.flags.native = std::stoi(parser.token);
@ -482,7 +482,7 @@ parse_error:
void brush_side_t::write_extended_info(std::ostream &stream) void brush_side_t::write_extended_info(std::ostream &stream)
{ {
if (extended_info) { if (extended_info) {
ewt::print(stream, " {} {} {}", extended_info->contents.native, extended_info->flags.native, extended_info->value); ewt::print(stream, " {} {} {}", extended_info->contents, extended_info->flags.native, extended_info->value);
} }
} }

View File

@ -28,6 +28,7 @@
#include <any> #include <any>
#include <optional> #include <optional>
#include <common/bitflags.hh>
#include <common/fs.hh> #include <common/fs.hh>
#include <common/qvec.hh> #include <common/qvec.hh>
#include <common/aabb.hh> #include <common/aabb.hh>
@ -47,26 +48,94 @@ struct lump_t
void stream_read(std::istream &s); void stream_read(std::istream &s);
}; };
using contents_int_t = uint64_t;
/**
* Superset of Q1- and Q2- features, plus EWT extensions.
*/
enum contents_t : contents_int_t {
EWT_VISCONTENTS_EMPTY = 0,
EWT_VISCONTENTS_SOLID = nth_bit<uint64_t>(0), // an eye is never valid in a solid
EWT_VISCONTENTS_SKY = nth_bit<uint64_t>(1),
EWT_VISCONTENTS_DETAIL_WALL = nth_bit<uint64_t>(2),
EWT_VISCONTENTS_WINDOW = nth_bit<uint64_t>(3), // translucent, but not watery (detail fence)
EWT_VISCONTENTS_AUX = nth_bit<uint64_t>(4),
EWT_VISCONTENTS_LAVA = nth_bit<uint64_t>(5),
EWT_VISCONTENTS_SLIME = nth_bit<uint64_t>(6),
EWT_VISCONTENTS_WATER = nth_bit<uint64_t>(7),
EWT_VISCONTENTS_MIST = nth_bit<uint64_t>(8),
EWT_LAST_VISIBLE_CONTENTS_INDEX = 8,
EWT_LAST_VISIBLE_CONTENTS = EWT_VISCONTENTS_MIST,
EWT_ALL_LIQUIDS =
EWT_VISCONTENTS_LAVA |
EWT_VISCONTENTS_SLIME |
EWT_VISCONTENTS_WATER,
EWT_ALL_VISIBLE_CONTENTS =
EWT_VISCONTENTS_SOLID |
EWT_VISCONTENTS_SKY |
EWT_VISCONTENTS_DETAIL_WALL |
EWT_VISCONTENTS_WINDOW |
EWT_VISCONTENTS_AUX |
EWT_VISCONTENTS_LAVA |
EWT_VISCONTENTS_SLIME |
EWT_VISCONTENTS_WATER |
EWT_VISCONTENTS_MIST,
EWT_INVISCONTENTS_ORIGIN = nth_bit<uint64_t>(9), // removed before bsping an entity
// Q1 clip
EWT_INVISCONTENTS_PLAYERCLIP = nth_bit<uint64_t>(10),
EWT_INVISCONTENTS_MONSTERCLIP = nth_bit<uint64_t>(11),
EWT_INVISCONTENTS_AREAPORTAL = nth_bit<uint64_t>(12),
EWT_INVISCONTENTS_ILLUSIONARY_VISBLOCKER = nth_bit<uint64_t>(13),
EWT_ALL_INVISCONTENTS =
EWT_INVISCONTENTS_ORIGIN |
EWT_INVISCONTENTS_PLAYERCLIP |
EWT_INVISCONTENTS_MONSTERCLIP |
EWT_INVISCONTENTS_AREAPORTAL |
EWT_INVISCONTENTS_ILLUSIONARY_VISBLOCKER,
EWT_CFLAG_DETAIL = nth_bit<uint64_t>(14), // brushes to be added after vis leafs
EWT_CFLAG_MIRROR_INSIDE = nth_bit<uint64_t>(15),
EWT_CFLAG_MIRROR_INSIDE_SET = nth_bit<uint64_t>(16),
EWT_CFLAG_SUPPRESS_CLIPPING_SAME_TYPE = nth_bit<uint64_t>(17),
EWT_CFLAG_CURRENT_0 = nth_bit<uint64_t>(18),
EWT_CFLAG_CURRENT_90 = nth_bit<uint64_t>(19),
EWT_CFLAG_CURRENT_180 = nth_bit<uint64_t>(20),
EWT_CFLAG_CURRENT_270 = nth_bit<uint64_t>(21),
EWT_CFLAG_CURRENT_UP = nth_bit<uint64_t>(22),
EWT_CFLAG_CURRENT_DOWN = nth_bit<uint64_t>(23),
EWT_CFLAG_TRANSLUCENT = nth_bit<uint64_t>(24), // auto set if any surface has trans,
EWT_CFLAG_LADDER = nth_bit<uint64_t>(25),
EWT_CFLAG_MONSTER = nth_bit<uint64_t>(26), // disallowed in maps, only for gamecode use
EWT_CFLAG_DEADMONSTER = nth_bit<uint64_t>(27), // disallowed in maps, only for gamecode use
// unused Q2 contents bits - just present here so we can roundtrip all 32-bit Q2 contents
EWT_CFLAG_Q2_UNUSED_7 = nth_bit<uint64_t>(28),
EWT_CFLAG_Q2_UNUSED_8 = nth_bit<uint64_t>(29),
EWT_CFLAG_Q2_UNUSED_9 = nth_bit<uint64_t>(30),
EWT_CFLAG_Q2_UNUSED_10 = nth_bit<uint64_t>(31),
EWT_CFLAG_Q2_UNUSED_11 = nth_bit<uint64_t>(32),
EWT_CFLAG_Q2_UNUSED_12 = nth_bit<uint64_t>(33),
EWT_CFLAG_Q2_UNUSED_13 = nth_bit<uint64_t>(34),
EWT_CFLAG_Q2_UNUSED_14 = nth_bit<uint64_t>(35),
EWT_CFLAG_Q2_UNUSED_30 = nth_bit<uint64_t>(36),
EWT_CFLAG_Q2_UNUSED_31 = nth_bit<uint64_t>(37)
};
struct gamedef_t; struct gamedef_t;
struct contentflags_t struct contentflags_t
{ {
// native flags value; what's written to the BSP basically contents_t flags;
int32_t native = 0;
// extra data supplied by the game static contentflags_t make(contents_int_t f) {
std::any game_data; return contentflags_t{.flags = static_cast<contents_t>(f)};
}
// the value set directly from `_mirrorinside` on the brush, if available.
// don't check this directly, use `is_mirror_inside` to allow the game to decide.
std::optional<bool> mirror_inside = std::nullopt;
// don't clip the same content type. mostly intended for CONTENTS_DETAIL_ILLUSIONARY.
// don't check this directly, use `will_clip_same_type` to allow the game to decide.
std::optional<bool> clips_same_type = std::nullopt;
// always blocks vis, even if it normally wouldn't
bool illusionary_visblocker = false;
bool equals(const gamedef_t *game, const contentflags_t &other) const; bool equals(const gamedef_t *game, const contentflags_t &other) const;
@ -77,10 +146,22 @@ struct contentflags_t
bool is_detail_fence(const gamedef_t *game) const; bool is_detail_fence(const gamedef_t *game) const;
bool is_detail_illusionary(const gamedef_t *game) const; bool is_detail_illusionary(const gamedef_t *game) const;
std::optional<bool> mirror_inside() const {
if (flags & EWT_CFLAG_MIRROR_INSIDE_SET) {
return {(flags & EWT_CFLAG_MIRROR_INSIDE) != 0};
}
return std::nullopt;
}
contentflags_t &set_mirrored(const std::optional<bool> &mirror_inside_value); contentflags_t &set_mirrored(const std::optional<bool> &mirror_inside_value);
inline bool will_clip_same_type(const gamedef_t *game) const { return will_clip_same_type(game, *this); } inline bool will_clip_same_type(const gamedef_t *game) const { return will_clip_same_type(game, *this); }
bool will_clip_same_type(const gamedef_t *game, const contentflags_t &other) const; bool will_clip_same_type(const gamedef_t *game, const contentflags_t &other) const;
std::optional<bool> clips_same_type() const {
if (flags & EWT_CFLAG_SUPPRESS_CLIPPING_SAME_TYPE) {
return {false};
}
return std::nullopt;
}
contentflags_t &set_clips_same_type(const std::optional<bool> &clips_same_type_value); contentflags_t &set_clips_same_type(const std::optional<bool> &clips_same_type_value);
bool is_empty(const gamedef_t *game) const; bool is_empty(const gamedef_t *game) const;
@ -112,6 +193,26 @@ struct contentflags_t
bool chops(const gamedef_t *game) const; bool chops(const gamedef_t *game) const;
std::string to_string(const gamedef_t *game) const; std::string to_string(const gamedef_t *game) const;
// returns the bit index (starting from 0) of the strongest visible content type
// set, or -1 if no visible content bits are set (i.e. EWT_VISCONTENTS_EMPTY)
int visible_contents_index() const {
for (uint32_t index = 0; nth_bit(index) <= EWT_LAST_VISIBLE_CONTENTS; ++index) {
if (flags & nth_bit(index)) {
return index;
}
}
return -1;
}
// returns the strongest EWT_VISCONTENTS_ bit, discarding all other flags
contentflags_t visible_contents() const {
int index = visible_contents_index();
if (index >= 0) {
return contentflags_t::make(static_cast<contents_t>(nth_bit(index)));
}
return contentflags_t::make(EWT_VISCONTENTS_EMPTY);
}
}; };
struct surfflags_t struct surfflags_t
@ -270,6 +371,8 @@ struct gamedef_t
virtual int32_t surfflags_from_string(const std::string_view &str) const = 0; virtual int32_t surfflags_from_string(const std::string_view &str) const = 0;
// FIXME: fix so that we don't have to pass a name here // FIXME: fix so that we don't have to pass a name here
virtual bool texinfo_is_hintskip(const surfflags_t &flags, const std::string &name) const = 0; virtual bool texinfo_is_hintskip(const surfflags_t &flags, const std::string &name) const = 0;
virtual contentflags_t create_contents_from_native(int32_t native) const = 0;
virtual int32_t contents_to_native(const contentflags_t &contents) const = 0;
virtual contentflags_t cluster_contents(const contentflags_t &contents0, const contentflags_t &contents1) const = 0; virtual contentflags_t cluster_contents(const contentflags_t &contents0, const contentflags_t &contents1) const = 0;
virtual contentflags_t create_empty_contents() const = 0; virtual contentflags_t create_empty_contents() const = 0;
virtual contentflags_t create_solid_contents() const = 0; virtual contentflags_t create_solid_contents() const = 0;

View File

@ -108,6 +108,16 @@ enum q2_contents_t : int32_t
Q2_CONTENTS_SLIME = nth_bit(4), Q2_CONTENTS_SLIME = nth_bit(4),
Q2_CONTENTS_WATER = nth_bit(5), Q2_CONTENTS_WATER = nth_bit(5),
Q2_CONTENTS_MIST = nth_bit(6), Q2_CONTENTS_MIST = nth_bit(6),
Q2_CONTENTS_UNUSED_7 = nth_bit(7),
Q2_CONTENTS_UNUSED_8 = nth_bit(8),
Q2_CONTENTS_UNUSED_9 = nth_bit(9),
Q2_CONTENTS_UNUSED_10 = nth_bit(10),
Q2_CONTENTS_UNUSED_11 = nth_bit(11),
Q2_CONTENTS_UNUSED_12 = nth_bit(12),
Q2_CONTENTS_UNUSED_13 = nth_bit(13),
Q2_CONTENTS_UNUSED_14 = nth_bit(14),
Q2_LAST_VISIBLE_CONTENTS = Q2_CONTENTS_MIST, Q2_LAST_VISIBLE_CONTENTS = Q2_CONTENTS_MIST,
Q2_ALL_VISIBLE_CONTENTS = Q2_CONTENTS_SOLID | Q2_CONTENTS_WINDOW | Q2_CONTENTS_AUX | Q2_CONTENTS_LAVA | Q2_ALL_VISIBLE_CONTENTS = Q2_CONTENTS_SOLID | Q2_CONTENTS_WINDOW | Q2_CONTENTS_AUX | Q2_CONTENTS_LAVA |
Q2_CONTENTS_SLIME | Q2_CONTENTS_WATER | Q2_CONTENTS_MIST, Q2_CONTENTS_SLIME | Q2_CONTENTS_WATER | Q2_CONTENTS_MIST,
@ -137,8 +147,8 @@ enum q2_contents_t : int32_t
Q2_CONTENTS_TRANSLUCENT = nth_bit(28), // auto set if any surface has trans Q2_CONTENTS_TRANSLUCENT = nth_bit(28), // auto set if any surface has trans
Q2_CONTENTS_LADDER = nth_bit(29), Q2_CONTENTS_LADDER = nth_bit(29),
// HACK: using Q2_CONTENTS_MONSTER for func_detail_wall Q2_CONTENTS_UNUSED_30 = nth_bit(30),
Q2_ALL_VISIBLE_CONTENTS_PLUS_MONSTER = Q2_ALL_VISIBLE_CONTENTS | Q2_CONTENTS_MONSTER, Q2_CONTENTS_UNUSED_31 = nth_bit(31)
}; };
struct q2_dnode_t struct q2_dnode_t

View File

@ -57,7 +57,7 @@ struct texture_meta
// Q2/WAL only // Q2/WAL only
surfflags_t flags{}; surfflags_t flags{};
contentflags_t contents{}; uint32_t contents_native = 0;
int32_t value = 0; int32_t value = 0;
std::string animation; std::string animation;
}; };

View File

@ -64,7 +64,7 @@ struct texdef_etp_t : texdef_quake_ed_t
// extra Q2 info // extra Q2 info
struct texinfo_quake2_t struct texinfo_quake2_t
{ {
contentflags_t contents; int contents;
surfflags_t flags; surfflags_t flags;
int value; int value;
}; };

View File

@ -72,7 +72,7 @@ enum class conversion_t
// used by Q2 format; used by various systems. // used by Q2 format; used by various systems.
struct extended_texinfo_t struct extended_texinfo_t
{ {
contentflags_t contents = {0}; uint32_t contents_native = 0;
surfflags_t flags = {0}; surfflags_t flags = {0};
int value = 0; int value = 0;
std::string animation; std::string animation;
@ -410,7 +410,6 @@ struct nodedata_t {
struct leafdata_t { struct leafdata_t {
// information for leafs // information for leafs
contentflags_t contents; // leaf nodes (0 for decision nodes)
std::vector<face_t *> markfaces; // leaf nodes only, point to node faces std::vector<face_t *> markfaces; // leaf nodes only, point to node faces
int outside_distance; // -1 = can't reach outside, 0 = first void node, >0 = distance from void, in number of int outside_distance; // -1 = can't reach outside, 0 = first void node, >0 = distance from void, in number of
// portals used to write leak lines that take the shortest path to the void // portals used to write leak lines that take the shortest path to the void
@ -419,6 +418,7 @@ struct leafdata_t {
uint32_t firstleafbrush; // Q2 uint32_t firstleafbrush; // Q2
uint32_t numleafbrushes; uint32_t numleafbrushes;
int32_t area; int32_t area;
contentflags_t contents; // leaf nodes (0 for decision nodes)
std::vector<bspbrush_t *> original_brushes; std::vector<bspbrush_t *> original_brushes;
bspbrush_t::container bsp_brushes; bspbrush_t::container bsp_brushes;
}; };

View File

@ -490,7 +490,8 @@ static bool Mod_LeafPvs(const mbsp_t *bsp, const mleaf_t *leaf, uint8_t *out)
static const std::vector<uint8_t> *Mod_LeafPvs(const mbsp_t *bsp, const mleaf_t *leaf) static const std::vector<uint8_t> *Mod_LeafPvs(const mbsp_t *bsp, const mleaf_t *leaf)
{ {
if (bsp->loadversion->game->contents_are_liquid({leaf->contents})) { if (bsp->loadversion->game->contents_are_liquid(
bsp->loadversion->game->create_contents_from_native(leaf->contents))) {
// the liquid case is because leaf->contents might be in an opaque liquid, // the liquid case is because leaf->contents might be in an opaque liquid,
// which we typically want light to pass through, but visdata would report that // which we typically want light to pass through, but visdata would report that
// there's no visibility across the opaque liquid. so, skip culling and do the raytracing. // there's no visibility across the opaque liquid. so, skip culling and do the raytracing.
@ -540,7 +541,8 @@ static void CalcPvs(const mbsp_t *bsp, lightsurf_t *lightsurf)
/* copy the pvs for this leaf into pointpvs */ /* copy the pvs for this leaf into pointpvs */
Mod_LeafPvs(bsp, leaf, pointpvs); Mod_LeafPvs(bsp, leaf, pointpvs);
if (bsp->loadversion->game->contents_are_liquid({leaf->contents})) { if (bsp->loadversion->game->contents_are_liquid(
bsp->loadversion->game->create_contents_from_native(leaf->contents))) {
// hack for when the sample point might be in an opaque liquid, blocking vis, // hack for when the sample point might be in an opaque liquid, blocking vis,
// but we typically want light to pass through these. // but we typically want light to pass through these.
// see also VisCullEntity() which handles the case when the light emitter is in liquid. // see also VisCullEntity() which handles the case when the light emitter is in liquid.
@ -1154,9 +1156,11 @@ static bool VisCullEntity(const mbsp_t *bsp, const std::vector<uint8_t> &pvs, co
return false; return false;
} }
if (bsp->loadversion->game->contents_are_solid({entleaf->contents}) || auto contents = bsp->loadversion->game->create_contents_from_native(entleaf->contents);
bsp->loadversion->game->contents_are_sky({entleaf->contents}) ||
bsp->loadversion->game->contents_are_liquid({entleaf->contents})) { if (bsp->loadversion->game->contents_are_solid(contents) ||
bsp->loadversion->game->contents_are_sky(contents) ||
bsp->loadversion->game->contents_are_liquid(contents)) {
// the liquid case is because entleaf->contents might be in an opaque liquid, // the liquid case is because entleaf->contents might be in an opaque liquid,
// which we typically want light to pass through, but visdata would report that // which we typically want light to pass through, but visdata would report that
// there's no visibility across the opaque liquid. so, skip culling and do the raytracing. // there's no visibility across the opaque liquid. so, skip culling and do the raytracing.

View File

@ -358,7 +358,7 @@ static void maputil_make_brush_side(lua_State *state, const brush_side_t &side)
if (side.extended_info) { if (side.extended_info) {
// set info // set info
lua_createtable(state, 0, 3); lua_createtable(state, 0, 3);
lua_pushnumber(state, side.extended_info->contents.native); lua_pushnumber(state, side.extended_info->contents);
lua_setfield(state, -2, "contents"); lua_setfield(state, -2, "contents");
lua_pushnumber(state, side.extended_info->value); lua_pushnumber(state, side.extended_info->value);
lua_setfield(state, -2, "value"); lua_setfield(state, -2, "value");
@ -538,7 +538,7 @@ static void maputil_copy_side(lua_State *state, brush_side_t &side)
texinfo_quake2_t q2; texinfo_quake2_t q2;
lua_getfield(state, -1, "contents"); lua_getfield(state, -1, "contents");
q2.contents.native = lua_tonumber(state, -1); q2.contents = lua_tonumber(state, -1);
lua_pop(state, 1); lua_pop(state, 1);
lua_getfield(state, -1, "value"); lua_getfield(state, -1, "value");
@ -716,7 +716,7 @@ static int l_load_texture_meta(lua_State *state)
lua_createtable(state, 0, 5); lua_createtable(state, 0, 5);
lua_pushnumber(state, result.contents.native); lua_pushnumber(state, result.contents_native);
lua_setfield(state, -2, "contents"); lua_setfield(state, -2, "contents");
lua_pushnumber(state, result.flags.native); lua_pushnumber(state, result.flags.native);
lua_setfield(state, -2, "flags"); lua_setfield(state, -2, "flags");

View File

@ -840,8 +840,8 @@ static void Brush_LoadEntity(mapentity_t &dst, mapentity_t &src, hull_index_t hu
// fixme-brushbsp: function calls above can override the values below // fixme-brushbsp: function calls above can override the values below
// so we have to re-set them to be sure they stay what the mapper intended.. // so we have to re-set them to be sure they stay what the mapper intended..
contents.set_mirrored(mapbrush.contents.mirror_inside); contents.set_mirrored(mapbrush.contents.mirror_inside());
contents.set_clips_same_type(mapbrush.contents.clips_same_type); contents.set_clips_same_type(mapbrush.contents.clips_same_type());
auto brush = LoadBrush(src, mapbrush, contents, hullnum, num_clipped); auto brush = LoadBrush(src, mapbrush, contents, hullnum, num_clipped);

View File

@ -92,7 +92,7 @@ static void ExportObjFace(
static void WriteContentsMaterial(std::ofstream &mtlf, contentflags_t contents, float r, float g, float b) static void WriteContentsMaterial(std::ofstream &mtlf, contentflags_t contents, float r, float g, float b)
{ {
// fixme-brushbsp // fixme-brushbsp
ewt::print(mtlf, "newmtl contents{}\n", contents.native); ewt::print(mtlf, "newmtl contents{}\n", qbsp_options.target_game->contents_to_native(contents));
mtlf << "Ka 0 0 0\n"; mtlf << "Ka 0 0 0\n";
ewt::print(mtlf, "Kd {} {} {}\n", r, g, b); ewt::print(mtlf, "Kd {} {} {}\n", r, g, b);
mtlf << "Ks 0 0 0\n"; mtlf << "Ks 0 0 0\n";
@ -105,14 +105,15 @@ void ExportObj_Faces(const std::string &filesuffix, const std::vector<const face
std::ofstream mtlfile = InitMtlFile(filesuffix); std::ofstream mtlfile = InitMtlFile(filesuffix);
WriteContentsMaterial(mtlfile, {}, 0, 0, 0); WriteContentsMaterial(mtlfile, {}, 0, 0, 0);
WriteContentsMaterial(mtlfile, {CONTENTS_EMPTY}, 0, 1, 0); // fixme-brushbsp
WriteContentsMaterial(mtlfile, {CONTENTS_SOLID}, 0.2, 0.2, 0.2); // WriteContentsMaterial(mtlfile, {CONTENTS_EMPTY}, 0, 1, 0);
// WriteContentsMaterial(mtlfile, {CONTENTS_SOLID}, 0.2, 0.2, 0.2);
WriteContentsMaterial(mtlfile, {CONTENTS_WATER}, 0.0, 0.0, 0.2); //
WriteContentsMaterial(mtlfile, {CONTENTS_SLIME}, 0.0, 0.2, 0.0); // WriteContentsMaterial(mtlfile, {CONTENTS_WATER}, 0.0, 0.0, 0.2);
WriteContentsMaterial(mtlfile, {CONTENTS_LAVA}, 0.2, 0.0, 0.0); // WriteContentsMaterial(mtlfile, {CONTENTS_SLIME}, 0.0, 0.2, 0.0);
// WriteContentsMaterial(mtlfile, {CONTENTS_LAVA}, 0.2, 0.0, 0.0);
WriteContentsMaterial(mtlfile, {CONTENTS_SKY}, 0.8, 0.8, 1.0); //
// WriteContentsMaterial(mtlfile, {CONTENTS_SKY}, 0.8, 0.8, 1.0);
// fixme-brushbsp // fixme-brushbsp
// WriteContentsMaterial(mtlfile, {CONTENTS_SOLID, CFLAGS_CLIP}, 1, 0.8, 0.8); // WriteContentsMaterial(mtlfile, {CONTENTS_SOLID, CFLAGS_CLIP}, 1, 0.8, 0.8);
// WriteContentsMaterial(mtlfile, {CONTENTS_EMPTY, CFLAGS_HINT}, 1, 1, 1); // WriteContentsMaterial(mtlfile, {CONTENTS_EMPTY, CFLAGS_HINT}, 1, 1, 1);
@ -121,7 +122,7 @@ void ExportObj_Faces(const std::string &filesuffix, const std::vector<const face
int vertcount = 0; int vertcount = 0;
for (const face_t *face : faces) { for (const face_t *face : faces) {
std::string mtlname = fmt::format("contents{}\n", face->contents.back.native); std::string mtlname = fmt::format("contents{}\n", qbsp_options.target_game->contents_to_native(face->contents.back));
ExportObjFace(objfile, mtlname, face->w, face->get_texinfo(), &vertcount); ExportObjFace(objfile, mtlname, face->w, face->get_texinfo(), &vertcount);
} }

View File

@ -462,7 +462,7 @@ int FindMiptex(const char *name, std::optional<extended_texinfo_t> &extended_inf
auto wal = map.load_image_meta(name); auto wal = map.load_image_meta(name);
if (wal && !internal && !extended_info.has_value()) { if (wal && !internal && !extended_info.has_value()) {
extended_info = extended_texinfo_t{wal->contents, wal->flags, wal->value, wal->animation}; extended_info = extended_texinfo_t{wal->contents_native, wal->flags, wal->value, wal->animation};
} }
if (!extended_info.has_value()) { if (!extended_info.has_value()) {
@ -906,7 +906,8 @@ static quark_tx_info_t ParseExtendedTX(parser_t &parser)
} else { } else {
// Parse extra Quake 2 surface info // Parse extra Quake 2 surface info
if (parser.parse_token(PARSE_OPTIONAL)) { if (parser.parse_token(PARSE_OPTIONAL)) {
result.info = extended_texinfo_t{{std::stoi(parser.token)}}; uint32_t native = std::stoul(parser.token);
result.info = extended_texinfo_t{.contents_native = native};
if (parser.parse_token(PARSE_OPTIONAL)) { if (parser.parse_token(PARSE_OPTIONAL)) {
result.info->flags.native = std::stoi(parser.token); result.info->flags.native = std::stoi(parser.token);
@ -1781,20 +1782,20 @@ static void ParseTextureDef(const mapentity_t &entity, parser_t &parser, mapface
// first one first // first one first
if (auto &wal = map.load_image_meta(mapface.texname.c_str())) { if (auto &wal = map.load_image_meta(mapface.texname.c_str())) {
if (!extinfo.info) { if (!extinfo.info) {
extinfo.info = extended_texinfo_t{wal->contents, wal->flags, wal->value}; extinfo.info = extended_texinfo_t{wal->contents_native, wal->flags, wal->value};
} }
extinfo.info->animation = wal->animation; extinfo.info->animation = wal->animation;
} else if (!extinfo.info) { } else if (!extinfo.info) {
extinfo.info = extended_texinfo_t{}; extinfo.info = extended_texinfo_t{};
} }
if (extinfo.info->contents.native & Q2_CONTENTS_TRANSLUCENT) { if (extinfo.info->contents_native & Q2_CONTENTS_TRANSLUCENT) {
// remove TRANSLUCENT; it's only meant to be set by the compiler // remove TRANSLUCENT; it's only meant to be set by the compiler
extinfo.info->contents.native &= ~Q2_CONTENTS_TRANSLUCENT; extinfo.info->contents_native &= ~Q2_CONTENTS_TRANSLUCENT;
// but give us detail if we lack trans. this is likely what they intended // but give us detail if we lack trans. this is likely what they intended
if (!(extinfo.info->flags.native & (Q2_SURF_TRANS33 | Q2_SURF_TRANS66))) { if (!(extinfo.info->flags.native & (Q2_SURF_TRANS33 | Q2_SURF_TRANS66))) {
extinfo.info->contents.native |= Q2_CONTENTS_DETAIL; extinfo.info->contents_native |= Q2_CONTENTS_DETAIL;
if (qbsp_options.verbose.value()) { if (qbsp_options.verbose.value()) {
logging::print("WARNING: {}: swapped TRANSLUCENT for DETAIL\n", mapface.line); logging::print("WARNING: {}: swapped TRANSLUCENT for DETAIL\n", mapface.line);
@ -1817,23 +1818,25 @@ static void ParseTextureDef(const mapentity_t &entity, parser_t &parser, mapface
// Mixing visible contents on the input brush is illegal // Mixing visible contents on the input brush is illegal
{ {
const int32_t visible_contents = extinfo.info->contents.native & Q2_ALL_VISIBLE_CONTENTS; const int32_t visible_contents = extinfo.info->contents_native & Q2_ALL_VISIBLE_CONTENTS;
// TODO: Move to bspfile.hh API // TODO: Move to bspfile.hh API
for (int32_t i = Q2_CONTENTS_SOLID; i <= Q2_LAST_VISIBLE_CONTENTS; i <<= 1) { for (int32_t i = Q2_CONTENTS_SOLID; i <= Q2_LAST_VISIBLE_CONTENTS; i <<= 1) {
if (visible_contents & i) { if (visible_contents & i) {
if (visible_contents != i) { if (visible_contents != i) {
FError("{}: Mixed visible contents: {}", mapface.line, FError("{}: Mixed visible contents: {}", mapface.line,
extinfo.info->contents.to_string(qbsp_options.target_game)); qbsp_options.target_game->create_contents_from_native(extinfo.info->contents_native)
.to_string(qbsp_options.target_game));
} }
} }
} }
} }
// Other Q2 hard errors // Other Q2 hard errors
if (extinfo.info->contents.native & (Q2_CONTENTS_MONSTER | Q2_CONTENTS_DEADMONSTER)) { if (extinfo.info->contents_native & (Q2_CONTENTS_MONSTER | Q2_CONTENTS_DEADMONSTER)) {
FError( FError(
"{}: Illegal contents: {}", mapface.line, extinfo.info->contents.to_string(qbsp_options.target_game)); "{}: Illegal contents: {}", mapface.line, qbsp_options.target_game->create_contents_from_native(
extinfo.info->contents_native).to_string(qbsp_options.target_game));
} }
// If Q2 style phong is enabled on a mirrored face, `light` will erroneously try to blend normals between // If Q2 style phong is enabled on a mirrored face, `light` will erroneously try to blend normals between
@ -1841,8 +1844,8 @@ static void ParseTextureDef(const mapentity_t &entity, parser_t &parser, mapface
const bool wants_phong = !(extinfo.info->flags.native & Q2_SURF_LIGHT) && (extinfo.info->value != 0); const bool wants_phong = !(extinfo.info->flags.native & Q2_SURF_LIGHT) && (extinfo.info->value != 0);
// Technically this is not the 100% correct check for mirrored, but we don't have the full brush // Technically this is not the 100% correct check for mirrored, but we don't have the full brush
// contents set up at this point. Correct would be to call `portal_generates_face()`. // contents set up at this point. Correct would be to call `portal_generates_face()`.
bool mirrored = (extinfo.info->contents.native != 0) && bool mirrored = (extinfo.info->contents_native != 0) &&
!(extinfo.info->contents.native & !(extinfo.info->contents_native &
(Q2_CONTENTS_DETAIL | Q2_CONTENTS_SOLID | Q2_CONTENTS_WINDOW | Q2_CONTENTS_AUX)); (Q2_CONTENTS_DETAIL | Q2_CONTENTS_SOLID | Q2_CONTENTS_WINDOW | Q2_CONTENTS_AUX));
if (entity.epairs.has("_mirrorinside") && !entity.epairs.get_int("_mirrorinside")) { if (entity.epairs.has("_mirrorinside") && !entity.epairs.get_int("_mirrorinside")) {
@ -1855,7 +1858,10 @@ static void ParseTextureDef(const mapentity_t &entity, parser_t &parser, mapface
} }
tx->miptex = FindMiptex(mapface.texname.c_str(), extinfo.info); tx->miptex = FindMiptex(mapface.texname.c_str(), extinfo.info);
mapface.contents = {extinfo.info->contents}; if (extinfo.info->contents_native != 0)
mapface.contents = qbsp_options.target_game->create_contents_from_native(extinfo.info->contents_native);
else
mapface.contents = contentflags_t::make(EWT_VISCONTENTS_EMPTY);
tx->flags = {extinfo.info->flags}; tx->flags = {extinfo.info->flags};
tx->value = extinfo.info->value; tx->value = extinfo.info->value;
@ -2485,8 +2491,9 @@ static contentflags_t Brush_GetContents(const mapentity_t &entity, const mapbrus
base_contents.set_clips_same_type(std::nullopt); base_contents.set_clips_same_type(std::nullopt);
} }
base_contents.illusionary_visblocker = if (string_iequals(entity.epairs.get("classname"), "func_illusionary_visblocker")) {
string_iequals(entity.epairs.get("classname"), "func_illusionary_visblocker"); base_contents = contentflags_t::make(base_contents.flags | EWT_INVISCONTENTS_ILLUSIONARY_VISBLOCKER);
}
// non-Q2: -transwater implies liquids are detail // non-Q2: -transwater implies liquids are detail
if (qbsp_options.target_game->id != GAME_QUAKE_II && qbsp_options.transwater.value()) { if (qbsp_options.target_game->id != GAME_QUAKE_II && qbsp_options.transwater.value()) {
@ -2996,10 +3003,10 @@ void ProcessAreaPortal(mapentity_t &entity)
} }
for (auto &brush : entity.mapbrushes) { for (auto &brush : entity.mapbrushes) {
brush.contents.native = Q2_CONTENTS_AREAPORTAL; brush.contents = contentflags_t::make(EWT_INVISCONTENTS_AREAPORTAL);
for (auto &face : brush.faces) { for (auto &face : brush.faces) {
face.contents.native = brush.contents.native; face.contents = brush.contents;
face.texinfo = map.skip_texinfo; face.texinfo = map.skip_texinfo;
} }
} }
@ -3530,7 +3537,7 @@ static void ConvertMapFace(std::ofstream &f, const mapface_t &mapface, const con
fprintDoubleAndSpc(f, quakeed.scale[1]); fprintDoubleAndSpc(f, quakeed.scale[1]);
if (mapface.raw_info.has_value()) { if (mapface.raw_info.has_value()) {
f << mapface.raw_info->contents.native << " " << mapface.raw_info->flags.native << " " f << mapface.raw_info->contents_native << " " << mapface.raw_info->flags.native << " "
<< mapface.raw_info->value; << mapface.raw_info->value;
} }
@ -3554,7 +3561,7 @@ static void ConvertMapFace(std::ofstream &f, const mapface_t &mapface, const con
fprintDoubleAndSpc(f, valve.scale[1]); fprintDoubleAndSpc(f, valve.scale[1]);
if (mapface.raw_info.has_value()) { if (mapface.raw_info.has_value()) {
f << mapface.raw_info->contents.native << " " << mapface.raw_info->flags.native << " " f << mapface.raw_info->contents_native << " " << mapface.raw_info->flags.native << " "
<< mapface.raw_info->value; << mapface.raw_info->value;
} }
@ -3580,7 +3587,7 @@ static void ConvertMapFace(std::ofstream &f, const mapface_t &mapface, const con
ewt::print(f, ") ) {} ", mapface.texname); ewt::print(f, ") ) {} ", mapface.texname);
if (mapface.raw_info.has_value()) { if (mapface.raw_info.has_value()) {
f << mapface.raw_info->contents.native << " " << mapface.raw_info->flags.native << " " f << mapface.raw_info->contents_native << " " << mapface.raw_info->flags.native << " "
<< mapface.raw_info->value; << mapface.raw_info->value;
} else { } else {
f << "0 0 0"; f << "0 0 0";

View File

@ -66,7 +66,8 @@ bool Portal_VisFlood(const portal_t *p)
contentflags_t contents1 = ClusterContents(p->nodes[1]); contentflags_t contents1 = ClusterContents(p->nodes[1]);
/* Can't see through func_illusionary_visblocker */ /* Can't see through func_illusionary_visblocker */
if (contents0.illusionary_visblocker || contents1.illusionary_visblocker) if ((contents0.flags & EWT_INVISCONTENTS_ILLUSIONARY_VISBLOCKER)
|| (contents1.flags & EWT_INVISCONTENTS_ILLUSIONARY_VISBLOCKER))
return false; return false;
// Check per-game visibility // Check per-game visibility
@ -613,7 +614,7 @@ static void FloodAreas_r(node_t *node)
auto *leafdata = node->get_leafdata(); auto *leafdata = node->get_leafdata();
Q_assert(leafdata); Q_assert(leafdata);
if (leafdata->contents.native & Q2_CONTENTS_AREAPORTAL) { if (leafdata->contents.flags & EWT_INVISCONTENTS_AREAPORTAL) {
// grab the func_areanode entity // grab the func_areanode entity
mapentity_t *entity = AreanodeEntityForLeaf(node); mapentity_t *entity = AreanodeEntityForLeaf(node);
@ -701,7 +702,7 @@ static void FindAreas_r(node_t *node)
// area portals are always only flooded into, never // area portals are always only flooded into, never
// out of // out of
if (leafdata->contents.native & Q2_CONTENTS_AREAPORTAL) if (leafdata->contents.flags & EWT_INVISCONTENTS_AREAPORTAL)
return; return;
map.c_areas++; map.c_areas++;
@ -781,7 +782,7 @@ static void FindAreaPortalExits_R(node_t *n, std::unordered_set<node_t *> &visit
continue; continue;
// is this an exit? // is this an exit?
if (!(neighbour->get_leafdata()->contents.native & Q2_CONTENTS_AREAPORTAL) && if (!(neighbour->get_leafdata()->contents.flags & EWT_INVISCONTENTS_AREAPORTAL) &&
!neighbour->get_leafdata()->contents.is_solid(qbsp_options.target_game)) { !neighbour->get_leafdata()->contents.is_solid(qbsp_options.target_game)) {
exits.emplace_back(p, neighbour); exits.emplace_back(p, neighbour);
continue; continue;
@ -789,7 +790,7 @@ static void FindAreaPortalExits_R(node_t *n, std::unordered_set<node_t *> &visit
// valid edge to explore? // valid edge to explore?
// if this isn't an exit, don't leave AREAPORTAL // if this isn't an exit, don't leave AREAPORTAL
if (!(neighbour->get_leafdata()->contents.native & Q2_CONTENTS_AREAPORTAL)) if (!(neighbour->get_leafdata()->contents.flags & EWT_INVISCONTENTS_AREAPORTAL))
continue; continue;
// continue exploding // continue exploding
@ -839,8 +840,8 @@ static void DebugAreaPortalBothSidesLeak(node_t *node)
return false; return false;
// don't go back into an areaportal // don't go back into an areaportal
if ((p->nodes[0]->get_leafdata()->contents.native & Q2_CONTENTS_AREAPORTAL) || if ((p->nodes[0]->get_leafdata()->contents.flags & EWT_INVISCONTENTS_AREAPORTAL) ||
(p->nodes[1]->get_leafdata()->contents.native & Q2_CONTENTS_AREAPORTAL)) (p->nodes[1]->get_leafdata()->contents.flags & EWT_INVISCONTENTS_AREAPORTAL))
return false; return false;
return true; return true;
@ -898,7 +899,7 @@ static void SetAreaPortalAreas_r(node_t *node)
auto *leafdata = node->get_leafdata(); auto *leafdata = node->get_leafdata();
if (leafdata->contents.native != Q2_CONTENTS_AREAPORTAL) if (leafdata->contents.flags != EWT_INVISCONTENTS_AREAPORTAL)
return; return;
if (leafdata->area) if (leafdata->area)

View File

@ -632,7 +632,9 @@ void qbsp_settings::load_texture_def(const std::string &pathname)
} }
if (parser.parse_token(PARSE_SAMELINE | PARSE_OPTIONAL)) { if (parser.parse_token(PARSE_SAMELINE | PARSE_OPTIONAL)) {
texinfo = extended_texinfo_t{{std::stoi(parser.token)}}; uint32_t native = std::stoul(parser.token);
texinfo = extended_texinfo_t{.contents_native = native};
if (parser.parse_token(PARSE_SAMELINE | PARSE_OPTIONAL)) { if (parser.parse_token(PARSE_SAMELINE | PARSE_OPTIONAL)) {
texinfo->flags.native = std::stoi(parser.token); texinfo->flags.native = std::stoi(parser.token);
@ -803,7 +805,9 @@ static void ExportBrushList_r(const mapentity_t &entity, node_t *node, brush_lis
{ {
if (node->is_leaf()) { if (node->is_leaf()) {
auto *leafdata = node->get_leafdata(); auto *leafdata = node->get_leafdata();
if (leafdata->contents.native) { int native = qbsp_options.target_game->contents_to_native(
qbsp_options.target_game->contents_remap_for_export(leafdata->contents, gamedef_t::remap_type_t::leaf));
if (native) {
if (leafdata->original_brushes.size()) { if (leafdata->original_brushes.size()) {
leafdata->numleafbrushes = leafdata->original_brushes.size(); leafdata->numleafbrushes = leafdata->original_brushes.size();
stats.total_leaf_brushes += leafdata->numleafbrushes; stats.total_leaf_brushes += leafdata->numleafbrushes;
@ -813,12 +817,13 @@ static void ExportBrushList_r(const mapentity_t &entity, node_t *node, brush_lis
if (!b->mapbrush->outputnumber.has_value()) { if (!b->mapbrush->outputnumber.has_value()) {
b->mapbrush->outputnumber = {static_cast<uint32_t>(map.bsp.dbrushes.size())}; b->mapbrush->outputnumber = {static_cast<uint32_t>(map.bsp.dbrushes.size())};
int brushcontents = qbsp_options.target_game->contents_to_native(qbsp_options.target_game
->contents_remap_for_export(b->contents, gamedef_t::remap_type_t::brush));
dbrush_t &brush = map.bsp.dbrushes.emplace_back( dbrush_t &brush = map.bsp.dbrushes.emplace_back(
dbrush_t{.firstside = static_cast<int32_t>(map.bsp.dbrushsides.size()), dbrush_t{.firstside = static_cast<int32_t>(map.bsp.dbrushsides.size()),
.numsides = 0, .numsides = 0,
.contents = qbsp_options.target_game .contents = brushcontents});
->contents_remap_for_export(b->contents, gamedef_t::remap_type_t::brush)
.native});
for (auto &side : b->mapbrush->faces) { for (auto &side : b->mapbrush->faces) {
@ -1348,8 +1353,9 @@ static bspxbrushes_permodel BSPX_Brushes_AddModel(int modelnum, const std::vecto
perbrush.bounds = b->bounds; perbrush.bounds = b->bounds;
const auto &contents = b->contents; const auto &contents = b->contents;
const int native = qbsp_options.target_game->contents_to_native(contents);
switch (contents.native) { switch (native) {
// contents should match the engine. // contents should match the engine.
case CONTENTS_EMPTY: // really an error, but whatever case CONTENTS_EMPTY: // really an error, but whatever
case CONTENTS_SOLID: // these are okay case CONTENTS_SOLID: // these are okay
@ -1360,7 +1366,7 @@ static bspxbrushes_permodel BSPX_Brushes_AddModel(int modelnum, const std::vecto
if (contents.is_clip(qbsp_options.target_game)) { if (contents.is_clip(qbsp_options.target_game)) {
perbrush.contents = BSPXBRUSHES_CONTENTS_CLIP; perbrush.contents = BSPXBRUSHES_CONTENTS_CLIP;
} else { } else {
perbrush.contents = contents.native; perbrush.contents = native;
} }
break; break;
// case CONTENTS_LADDER: // case CONTENTS_LADDER:

View File

@ -114,7 +114,7 @@ ExportClipNodes
static size_t ExportClipNodes(node_t *node) static size_t ExportClipNodes(node_t *node)
{ {
if (auto *leafdata = node->get_leafdata()) { if (auto *leafdata = node->get_leafdata()) {
return leafdata->contents.native; return qbsp_options.target_game->contents_to_native(leafdata->contents);
} }
auto *nodedata = node->get_nodedata(); auto *nodedata = node->get_nodedata();
@ -173,7 +173,7 @@ static void ExportLeaf(node_t *node)
remapped.to_string(qbsp_options.target_game)); remapped.to_string(qbsp_options.target_game));
} }
dleaf.contents = remapped.native; dleaf.contents = qbsp_options.target_game->contents_to_native(remapped);
if (node->bounds.maxs()[0] < node->bounds.mins()[0]) { if (node->bounds.maxs()[0] < node->bounds.mins()[0]) {
throw std::runtime_error("leaf bounds was unassigned"); throw std::runtime_error("leaf bounds was unassigned");
@ -330,7 +330,7 @@ void BeginBSPFile()
// Leave room for leaf 0 (must be solid) // Leave room for leaf 0 (must be solid)
auto &solid_leaf = map.bsp.dleafs.emplace_back(); auto &solid_leaf = map.bsp.dleafs.emplace_back();
solid_leaf.contents = qbsp_options.target_game->create_solid_contents().native; solid_leaf.contents = qbsp_options.target_game->contents_to_native(qbsp_options.target_game->create_solid_contents());
solid_leaf.cluster = CLUSTER_INVALID; solid_leaf.cluster = CLUSTER_INVALID;
Q_assert(map.bsp.dleafs.size() == 1); Q_assert(map.bsp.dleafs.size() == 1);
} }

View File

@ -27,21 +27,22 @@ TEST_SUITE("common")
const auto detail_fence = game_q1->create_detail_fence_contents(solid); const auto detail_fence = game_q1->create_detail_fence_contents(solid);
const auto detail_illusionary = game_q1->create_detail_illusionary_contents(solid); const auto detail_illusionary = game_q1->create_detail_illusionary_contents(solid);
const std::array test_contents{contentflags_t{CONTENTS_EMPTY}, contentflags_t{CONTENTS_SOLID}, const std::array test_contents{game_q1->create_contents_from_native(CONTENTS_EMPTY),
contentflags_t{CONTENTS_WATER}, contentflags_t{CONTENTS_SLIME}, contentflags_t{CONTENTS_LAVA}, game_q1->create_contents_from_native(CONTENTS_SOLID),
contentflags_t{CONTENTS_SKY}, game_q1->create_contents_from_native(CONTENTS_WATER),
game_q1->create_contents_from_native(CONTENTS_SLIME),
game_q1->create_contents_from_native(CONTENTS_LAVA),
game_q1->create_contents_from_native(CONTENTS_SKY),
detail_solid, detail_wall, detail_fence, detail_illusionary}; detail_solid, detail_wall, detail_fence, detail_illusionary};
SUBCASE("solid combined with others") SUBCASE("solid combined with others")
{ {
CHECK(solid.native == CONTENTS_SOLID); CHECK(game_q1->contents_to_native(solid) == CONTENTS_SOLID);
CHECK(!solid.game_data.has_value());
for (const auto &c : test_contents) { for (const auto &c : test_contents) {
auto combined = game_q1->combine_contents(solid, c); auto combined = game_q1->combine_contents(solid, c);
CHECK(combined.native == CONTENTS_SOLID); CHECK(game_q1->contents_to_native(combined) == CONTENTS_SOLID);
CHECK(combined.is_solid(game_q1)); CHECK(combined.is_solid(game_q1));
CHECK(!combined.is_any_detail(game_q1)); CHECK(!combined.is_any_detail(game_q1));
@ -50,15 +51,15 @@ TEST_SUITE("common")
SUBCASE("detail_illusionary plus water") SUBCASE("detail_illusionary plus water")
{ {
auto combined = game_q1->combine_contents(detail_illusionary, contentflags_t{CONTENTS_WATER}); auto combined = game_q1->combine_contents(detail_illusionary, game_q1->create_contents_from_native(CONTENTS_WATER));
CHECK(combined.native == CONTENTS_WATER); CHECK(game_q1->contents_to_native(combined) == CONTENTS_WATER);
CHECK(combined.is_detail_illusionary(game_q1)); CHECK(combined.is_detail_illusionary(game_q1));
} }
SUBCASE("detail_solid plus water") SUBCASE("detail_solid plus water")
{ {
auto combined = game_q1->combine_contents(detail_solid, contentflags_t{CONTENTS_WATER}); auto combined = game_q1->combine_contents(detail_solid, game_q1->create_contents_from_native(CONTENTS_WATER));
CHECK(combined.is_any_solid(game_q1)); CHECK(combined.is_any_solid(game_q1));
CHECK(combined.is_detail_solid(game_q1)); CHECK(combined.is_detail_solid(game_q1));
@ -68,7 +69,7 @@ TEST_SUITE("common")
SUBCASE("detail_solid plus sky") SUBCASE("detail_solid plus sky")
{ {
auto combined = game_q1->combine_contents(detail_solid, contentflags_t{CONTENTS_SKY}); auto combined = game_q1->combine_contents(detail_solid, game_q1->create_contents_from_native(CONTENTS_SKY));
CHECK(!combined.is_detail_solid(game_q1)); CHECK(!combined.is_detail_solid(game_q1));
CHECK(combined.is_sky(game_q1)); CHECK(combined.is_sky(game_q1));
@ -123,7 +124,7 @@ TEST_SUITE("common")
{ {
auto *game = bspver_q2.game; auto *game = bspver_q2.game;
auto origin = game->face_get_contents("", {}, {Q2_CONTENTS_ORIGIN}); auto origin = game->face_get_contents("", {}, game->create_contents_from_native(Q2_CONTENTS_ORIGIN));
CHECK(origin.is_origin(game)); CHECK(origin.is_origin(game));
CHECK(!origin.is_empty(game)); CHECK(!origin.is_empty(game));
@ -219,24 +220,30 @@ TEST_SUITE("common")
TEST_CASE("q2 contents") TEST_CASE("q2 contents")
{ {
const std::array test_contents{contentflags_t{Q2_CONTENTS_EMPTY}, contentflags_t{Q2_CONTENTS_SOLID},
contentflags_t{Q2_CONTENTS_WINDOW}, contentflags_t{Q2_CONTENTS_AUX}, contentflags_t{Q2_CONTENTS_LAVA},
contentflags_t{Q2_CONTENTS_SLIME}, contentflags_t{Q2_CONTENTS_WATER}, contentflags_t{Q2_CONTENTS_MIST},
contentflags_t{Q2_CONTENTS_DETAIL | Q2_CONTENTS_SOLID},
contentflags_t{Q2_CONTENTS_DETAIL | Q2_CONTENTS_WINDOW},
contentflags_t{Q2_CONTENTS_DETAIL | Q2_CONTENTS_AUX}, contentflags_t{Q2_CONTENTS_DETAIL | Q2_CONTENTS_LAVA},
contentflags_t{Q2_CONTENTS_DETAIL | Q2_CONTENTS_SLIME},
contentflags_t{Q2_CONTENTS_DETAIL | Q2_CONTENTS_WATER},
contentflags_t{Q2_CONTENTS_DETAIL | Q2_CONTENTS_MIST}};
auto *game_q2 = bspver_q2.game; auto *game_q2 = bspver_q2.game;
const std::array test_contents{
game_q2->create_contents_from_native(Q2_CONTENTS_EMPTY),
game_q2->create_contents_from_native(Q2_CONTENTS_SOLID),
game_q2->create_contents_from_native(Q2_CONTENTS_WINDOW),
game_q2->create_contents_from_native(Q2_CONTENTS_AUX),
game_q2->create_contents_from_native(Q2_CONTENTS_LAVA),
game_q2->create_contents_from_native(Q2_CONTENTS_SLIME),
game_q2->create_contents_from_native(Q2_CONTENTS_WATER),
game_q2->create_contents_from_native(Q2_CONTENTS_MIST),
game_q2->create_contents_from_native(Q2_CONTENTS_DETAIL | Q2_CONTENTS_SOLID),
game_q2->create_contents_from_native(Q2_CONTENTS_DETAIL | Q2_CONTENTS_WINDOW),
game_q2->create_contents_from_native(Q2_CONTENTS_DETAIL | Q2_CONTENTS_AUX),
game_q2->create_contents_from_native(Q2_CONTENTS_DETAIL | Q2_CONTENTS_LAVA),
game_q2->create_contents_from_native(Q2_CONTENTS_DETAIL | Q2_CONTENTS_SLIME),
game_q2->create_contents_from_native(Q2_CONTENTS_DETAIL | Q2_CONTENTS_WATER),
game_q2->create_contents_from_native(Q2_CONTENTS_DETAIL | Q2_CONTENTS_MIST)
};
SUBCASE("solid combined with others") SUBCASE("solid combined with others")
{ {
auto solid = game_q2->create_solid_contents(); auto solid = game_q2->create_solid_contents();
CHECK(solid.native == Q2_CONTENTS_SOLID); CHECK(game_q2->contents_to_native(solid) == Q2_CONTENTS_SOLID);
CHECK(!solid.game_data.has_value());
for (const auto &c : test_contents) { for (const auto &c : test_contents) {
// solid is treated specially in Q2 and wipes out any other content // solid is treated specially in Q2 and wipes out any other content
@ -244,30 +251,60 @@ TEST_SUITE("common")
auto combined = game_q2->contents_remap_for_export( auto combined = game_q2->contents_remap_for_export(
game_q2->combine_contents(solid, c), gamedef_t::remap_type_t::leaf); game_q2->combine_contents(solid, c), gamedef_t::remap_type_t::leaf);
CHECK(combined.native == Q2_CONTENTS_SOLID); CHECK(game_q2->contents_to_native(combined) == Q2_CONTENTS_SOLID);
CHECK(!combined.game_data.has_value());
CHECK(combined.is_solid(game_q2)); CHECK(combined.is_solid(game_q2));
} }
} }
SUBCASE("water combined with others") SUBCASE("water combined with others")
{ {
contentflags_t water{Q2_CONTENTS_WATER}; contentflags_t water = game_q2->create_contents_from_native(Q2_CONTENTS_WATER);
for (const auto &c : test_contents) { for (const auto &c : test_contents) {
auto combined = game_q2->combine_contents(water, c); auto combined = game_q2->combine_contents(water, c);
CHECK(!combined.game_data.has_value());
SUBCASE(fmt::format("water combined with {}", c.to_string(game_q2)).c_str()) SUBCASE(fmt::format("water combined with {}", c.to_string(game_q2)).c_str())
{ {
if (!(c.native & Q2_CONTENTS_SOLID)) { if (!(game_q2->contents_to_native(c) & Q2_CONTENTS_SOLID)) {
CHECK(combined.native == (Q2_CONTENTS_WATER | c.native)); CHECK(game_q2->contents_to_native(combined) == (Q2_CONTENTS_WATER | game_q2->contents_to_native(c)));
} }
} }
} }
} }
} }
TEST_CASE("q1 contents roundtrip")
{
auto *game_q1 = bspver_q1.game;
for (int i = CONTENTS_EMPTY; i >= CONTENTS_MIN; --i) {
contentflags_t test_internal = game_q1->create_contents_from_native(i);
uint32_t test_out = game_q1->contents_to_native(test_internal);
INFO("contents " << i);
CHECK(test_out == i);
}
}
TEST_CASE("q2 contents roundtrip")
{
auto *game_q2 = bspver_q2.game;
CHECK(game_q2->contents_to_native(game_q2->create_contents_from_native(0)) == 0);
for (int i = 0; i <= 31; ++i) {
uint32_t test_in = nth_bit<uint32_t>(i);
contentflags_t test_internal = game_q2->create_contents_from_native(test_in);
uint32_t test_out = game_q2->contents_to_native(test_internal);
INFO("contents bit " << i);
CHECK(test_out == test_in);
}
}
TEST_CASE("imglib png loader") TEST_CASE("imglib png loader")
{ {
auto *game = bspver_q2.game; auto *game = bspver_q2.game;

View File

@ -47,6 +47,7 @@ static testresults_t QbspVisLight_Common(const std::filesystem::path &name, std:
args.push_back("-noverbose"); args.push_back("-noverbose");
} else { } else {
args.push_back("-nopercent"); args.push_back("-nopercent");
args.push_back("-loghulls");
} }
for (auto &extra : extra_qbsp_args) { for (auto &extra : extra_qbsp_args) {
args.push_back(extra); args.push_back(extra);

View File

@ -17,12 +17,16 @@ int main(int argc, char **argv)
for (int i = 1; i < argc; ++i) { for (int i = 1; i < argc; ++i) {
// parse "-threads 1" // parse "-threads 1"
if (!strcmp("-threads", argv[i]) && (i + 1) < argc) { if (!strcmp("-threads", argv[i]) || !strcmp("--threads", argv[i])) {
if (!(i + 1 < argc)) {
logging::print("--threads requires an argument\n");
exit(1);
}
configureTBB(atoi(argv[i + 1]), false); configureTBB(atoi(argv[i + 1]), false);
continue; continue;
} }
// parse "-verbose" // parse "-verbose"
if (!strcmp("-verbose", argv[i])) { if (!strcmp("-verbose", argv[i]) || !strcmp("--verbose", argv[i])) {
tests_verbose = true; tests_verbose = true;
continue; continue;
} }

View File

@ -106,6 +106,7 @@ std::tuple<mbsp_t, bspxentries_t, std::optional<prtfile_t>> LoadTestmap(
args.push_back("-noverbose"); args.push_back("-noverbose");
} else { } else {
args.push_back("-nopercent"); args.push_back("-nopercent");
args.push_back("-loghulls");
} }
for (auto &arg : extra_args) { for (auto &arg : extra_args) {
@ -351,7 +352,9 @@ TEST_CASE("duplicatePlanes" * doctest::test_suite("qbsp"))
REQUIRE(1 == worldspawn.mapbrushes.size()); REQUIRE(1 == worldspawn.mapbrushes.size());
CHECK(6 == worldspawn.mapbrushes.front().faces.size()); CHECK(6 == worldspawn.mapbrushes.front().faces.size());
auto brush = LoadBrush(worldspawn, worldspawn.mapbrushes.front(), {CONTENTS_SOLID}, 0, std::nullopt); auto *game = bspver_q1.game;
auto brush = LoadBrush(worldspawn, worldspawn.mapbrushes.front(), game->create_contents_from_native(CONTENTS_SOLID), 0, std::nullopt);
CHECK(6 == brush->sides.size()); CHECK(6 == brush->sides.size());
} }
@ -1413,13 +1416,15 @@ TEST_CASE("q1_sealing" * doctest::test_suite("testmaps_q1"))
TEST_CASE("q1_csg" * doctest::test_suite("testmaps_q1")) TEST_CASE("q1_csg" * doctest::test_suite("testmaps_q1"))
{ {
auto *game = bspver_q1.game;
auto &entity = LoadMapPath("q1_csg.map"); auto &entity = LoadMapPath("q1_csg.map");
REQUIRE(entity.mapbrushes.size() == 2); REQUIRE(entity.mapbrushes.size() == 2);
bspbrush_t::container bspbrushes; bspbrush_t::container bspbrushes;
for (int i = 0; i < 2; ++i) { for (int i = 0; i < 2; ++i) {
auto b = LoadBrush(entity, entity.mapbrushes[i], {CONTENTS_SOLID}, 0, std::nullopt); auto b = LoadBrush(entity, entity.mapbrushes[i], game->create_contents_from_native(CONTENTS_SOLID), 0, std::nullopt);
CHECK(6 == b->sides.size()); CHECK(6 == b->sides.size());

View File

@ -773,13 +773,13 @@ TEST_CASE("q2_detail_wall" * doctest::test_suite("testmaps_q2"))
{ {
INFO("check leaf / brush contents"); INFO("check leaf / brush contents");
CAPTURE(contentflags_t{detail_wall_leaf->contents}.to_string(game)); CAPTURE(game->create_contents_from_native(detail_wall_leaf->contents).to_string(game));
CHECK(Q2_CONTENTS_SOLID == detail_wall_leaf->contents); CHECK(Q2_CONTENTS_SOLID == detail_wall_leaf->contents);
REQUIRE(1 == Leaf_Brushes(&bsp, detail_wall_leaf).size()); REQUIRE(1 == Leaf_Brushes(&bsp, detail_wall_leaf).size());
auto *brush = Leaf_Brushes(&bsp, detail_wall_leaf).at(0); auto *brush = Leaf_Brushes(&bsp, detail_wall_leaf).at(0);
CAPTURE(contentflags_t{brush->contents}.to_string(game)); CAPTURE(game->create_contents_from_native(brush->contents).to_string(game));
CHECK(Q2_CONTENTS_SOLID == brush->contents); CHECK(Q2_CONTENTS_SOLID == brush->contents);
} }
@ -817,7 +817,7 @@ TEST_CASE("q2_detail_fence" * doctest::test_suite("testmaps_q2"))
{ {
INFO("check leaf / brush contents"); INFO("check leaf / brush contents");
CAPTURE(contentflags_t{detail_wall_leaf->contents}.to_string(game)); CAPTURE(game->create_contents_from_native(detail_wall_leaf->contents).to_string(game));
CHECK( CHECK(
(Q2_CONTENTS_WINDOW | Q2_CONTENTS_DETAIL | Q2_CONTENTS_TRANSLUCENT) == detail_wall_leaf->contents); (Q2_CONTENTS_WINDOW | Q2_CONTENTS_DETAIL | Q2_CONTENTS_TRANSLUCENT) == detail_wall_leaf->contents);