Merge branch 'brushbsp' of https://github.com/ericwa/ericw-tools into brushbsp

This commit is contained in:
Eric Wasylishen 2022-06-11 14:07:21 -06:00
commit c5b84d9eb7
9 changed files with 248 additions and 132 deletions

View File

@ -54,14 +54,10 @@ struct gamedef_generic_t : public gamedef_t
contentflags_t cluster_contents(const contentflags_t &, const contentflags_t &) const { throw std::bad_cast(); } contentflags_t cluster_contents(const contentflags_t &, const contentflags_t &) const { throw std::bad_cast(); }
int32_t get_content_type(const contentflags_t &) const { throw std::bad_cast(); }
int32_t contents_priority(const contentflags_t &) const { throw std::bad_cast(); } int32_t contents_priority(const contentflags_t &) const { throw std::bad_cast(); }
bool chops(const contentflags_t &) const { throw std::bad_cast(); } bool chops(const contentflags_t &) const { throw std::bad_cast(); }
contentflags_t create_extended_contents(const uint16_t &) const { throw std::bad_cast(); }
contentflags_t create_empty_contents(const uint16_t &) const { throw std::bad_cast(); } contentflags_t create_empty_contents(const uint16_t &) const { throw std::bad_cast(); }
contentflags_t create_solid_contents(const uint16_t &) const { throw std::bad_cast(); } contentflags_t create_solid_contents(const uint16_t &) const { throw std::bad_cast(); }
@ -76,6 +72,10 @@ struct gamedef_generic_t : public gamedef_t
contentflags_t create_detail_solid_contents(const contentflags_t &original) const { throw std::bad_cast(); } contentflags_t create_detail_solid_contents(const contentflags_t &original) const { throw std::bad_cast(); }
bool contents_are_type_equal(const contentflags_t &self, const contentflags_t &other) const { throw std::bad_cast(); }
bool contents_are_equal(const contentflags_t &self, const contentflags_t &other) const { throw std::bad_cast(); }
bool contents_are_any_detail(const contentflags_t &) const { throw std::bad_cast(); } bool contents_are_any_detail(const contentflags_t &) const { throw std::bad_cast(); }
bool contents_are_detail_solid(const contentflags_t &contents) const { throw std::bad_cast(); } bool contents_are_detail_solid(const contentflags_t &contents) const { throw std::bad_cast(); }
@ -85,9 +85,15 @@ struct gamedef_generic_t : public gamedef_t
bool contents_are_detail_illusionary(const contentflags_t &contents) const { throw std::bad_cast(); } bool contents_are_detail_illusionary(const contentflags_t &contents) const { throw std::bad_cast(); }
bool contents_are_empty(const contentflags_t &) const { throw std::bad_cast(); } bool contents_are_empty(const contentflags_t &) const { throw std::bad_cast(); }
bool contents_are_mirrored(const contentflags_t &) const { throw std::bad_cast(); } bool contents_are_mirrored(const contentflags_t &) const { throw std::bad_cast(); }
bool contents_are_origin(const contentflags_t &contents) const { throw std::bad_cast(); }
bool contents_are_clip(const contentflags_t &contents) const { throw std::bad_cast(); }
bool contents_clip_same_type(const contentflags_t &, const contentflags_t &) const { throw std::bad_cast(); }
bool contents_are_solid(const contentflags_t &) const { throw std::bad_cast(); } bool contents_are_solid(const contentflags_t &) const { throw std::bad_cast(); }
bool contents_are_sky(const contentflags_t &) const { throw std::bad_cast(); } bool contents_are_sky(const contentflags_t &) const { throw std::bad_cast(); }
@ -116,6 +122,19 @@ struct gamedef_generic_t : public gamedef_t
const std::vector<qvec3b> &get_default_palette() const { throw std::bad_cast(); }; const std::vector<qvec3b> &get_default_palette() const { throw std::bad_cast(); };
}; };
// extra data for contentflags_t for Quake-like
struct q1_contentflags_data
{
bool origin = false; // is an origin brush
bool clip = false; // is a clip brush
constexpr bool operator==(const q1_contentflags_data &other) const { return origin == other.origin && clip == other.clip; }
constexpr bool operator!=(const q1_contentflags_data &other) const { return !(*this == other); }
constexpr explicit operator bool() const { return origin || clip; }
};
template<gameid_t ID> template<gameid_t ID>
struct gamedef_q1_like_t : public gamedef_t struct gamedef_q1_like_t : public gamedef_t
{ {
@ -139,7 +158,7 @@ struct gamedef_q1_like_t : public gamedef_t
contentflags_t cluster_contents(const contentflags_t &contents0, const contentflags_t &contents1) const contentflags_t cluster_contents(const contentflags_t &contents0, const contentflags_t &contents1) const
{ {
if (contents0 == contents1) if (contents0.equals(this, contents1))
return contents0; return contents0;
/* /*
@ -159,8 +178,6 @@ struct gamedef_q1_like_t : public gamedef_t
return create_solid_contents(); return create_solid_contents();
} }
int32_t get_content_type(const contentflags_t &contents) const { return contents.native; }
int32_t contents_priority(const contentflags_t &contents) const int32_t contents_priority(const contentflags_t &contents) const
{ {
if (contents.extended & CFLAGS_DETAIL) { if (contents.extended & CFLAGS_DETAIL) {
@ -193,7 +210,7 @@ struct gamedef_q1_like_t : public gamedef_t
return contents_are_solid(contents) || contents_are_sky(contents) || (contents.extended & CFLAGS_DETAIL); return contents_are_solid(contents) || contents_are_sky(contents) || (contents.extended & CFLAGS_DETAIL);
} }
contentflags_t create_extended_contents(const uint16_t &cflags) const { return {0, cflags}; } inline contentflags_t create_extended_contents(const q1_contentflags_data &data) const { return {0, 0, data}; }
contentflags_t create_empty_contents(const uint16_t &cflags = 0) const contentflags_t create_empty_contents(const uint16_t &cflags = 0) const
{ {
@ -236,6 +253,28 @@ struct gamedef_q1_like_t : public gamedef_t
return {0, CFLAGS_DETAIL}; return {0, CFLAGS_DETAIL};
} }
bool contents_are_type_equal(const contentflags_t &self, const contentflags_t &other) const
{
if (self.game_data.has_value() != other.game_data.has_value()) {
return false;
}
if (self.game_data.has_value()) {
if (std::any_cast<const q1_contentflags_data &>(self.game_data) !=
std::any_cast<const q1_contentflags_data &>(other.game_data)) {
return false;
}
}
return (self.extended & CFLAGS_CONTENTS_MASK) == (other.extended & CFLAGS_CONTENTS_MASK) &&
self.native == other.native;
}
bool contents_are_equal(const contentflags_t &self, const contentflags_t &other) const
{
return contents_are_type_equal(self, other);
}
bool contents_are_any_detail(const contentflags_t &contents) const bool contents_are_any_detail(const contentflags_t &contents) const
{ {
// in Q1, there are only CFLAGS_DETAIL, CFLAGS_DETAIL_ILLUSIONARY, or CFLAGS_DETAIL_FENCE // in Q1, there are only CFLAGS_DETAIL, CFLAGS_DETAIL_ILLUSIONARY, or CFLAGS_DETAIL_FENCE
@ -257,18 +296,11 @@ struct gamedef_q1_like_t : public gamedef_t
return ((contents.extended & CFLAGS_DETAIL_ILLUSIONARY) != 0); return ((contents.extended & CFLAGS_DETAIL_ILLUSIONARY) != 0);
} }
bool contents_are_empty(const contentflags_t &contents) const
{
if (contents.extended & CFLAGS_CONTENTS_MASK)
return false;
return contents.native == CONTENTS_EMPTY;
}
bool contents_are_mirrored(const contentflags_t &contents) const bool contents_are_mirrored(const contentflags_t &contents) const
{ {
if (contents.extended & CFLAGS_BMODEL_MIRROR_INSIDE) { // if we have mirrorinside set, go ahead
return true; if (contents.mirror_inside.has_value()) {
return contents.mirror_inside.value();
} }
// If the brush is non-solid, mirror faces for the inside view // If the brush is non-solid, mirror faces for the inside view
@ -277,10 +309,37 @@ struct gamedef_q1_like_t : public gamedef_t
|| (contents.native == CONTENTS_LAVA); || (contents.native == CONTENTS_LAVA);
} }
bool contents_are_origin(const contentflags_t &contents) const
{
return contents.game_data.has_value() && std::any_cast<const q1_contentflags_data &>(contents.game_data).origin;
}
bool contents_are_clip(const contentflags_t &contents) const
{
return contents.game_data.has_value() && std::any_cast<const q1_contentflags_data &>(contents.game_data).clip;
}
bool contents_clip_same_type(const contentflags_t &self, const contentflags_t &other) const
{
return self.equals(this, other) && self.clips_same_type.value_or(true);
}
bool contents_are_empty(const contentflags_t &contents) const
{
if (contents.extended & CFLAGS_CONTENTS_MASK)
return false;
else if (contents.game_data.has_value() && std::any_cast<const q1_contentflags_data &>(contents.game_data))
return false;
return contents.native == CONTENTS_EMPTY;
}
bool contents_are_solid(const contentflags_t &contents) const bool contents_are_solid(const contentflags_t &contents) const
{ {
if (contents.extended & CFLAGS_CONTENTS_MASK) if (contents.extended & CFLAGS_CONTENTS_MASK)
return false; return false;
else if (contents.game_data.has_value() && std::any_cast<const q1_contentflags_data &>(contents.game_data))
return false;
return contents.native == CONTENTS_SOLID; return contents.native == CONTENTS_SOLID;
} }
@ -289,6 +348,8 @@ struct gamedef_q1_like_t : public gamedef_t
{ {
if (contents.extended & CFLAGS_CONTENTS_MASK) if (contents.extended & CFLAGS_CONTENTS_MASK)
return false; return false;
else if (contents.game_data.has_value() && std::any_cast<const q1_contentflags_data &>(contents.game_data))
return false;
return contents.native == CONTENTS_SKY; return contents.native == CONTENTS_SKY;
} }
@ -297,6 +358,8 @@ struct gamedef_q1_like_t : public gamedef_t
{ {
if (contents.extended & CFLAGS_CONTENTS_MASK) if (contents.extended & CFLAGS_CONTENTS_MASK)
return false; return false;
else if (contents.game_data.has_value() && std::any_cast<const q1_contentflags_data &>(contents.game_data))
return false;
return contents.native <= CONTENTS_WATER && contents.native >= CONTENTS_LAVA; return contents.native <= CONTENTS_WATER && contents.native >= CONTENTS_LAVA;
} }
@ -321,7 +384,7 @@ struct gamedef_q1_like_t : public gamedef_t
bool portal_can_see_through(const contentflags_t &contents0, const contentflags_t &contents1) const bool portal_can_see_through(const contentflags_t &contents0, const contentflags_t &contents1) const
{ {
/* If contents values are the same and not solid, can see through */ /* If contents values are the same and not solid, can see through */
return !(contents0.is_solid(this) || contents1.is_solid(this)) && contents0 == contents1; return !(contents0.is_solid(this) || contents1.is_solid(this)) && contents0.equals(this, contents1);
} }
bool contents_seals_map(const contentflags_t &contents) const override bool contents_seals_map(const contentflags_t &contents) const override
@ -367,11 +430,11 @@ struct gamedef_q1_like_t : public gamedef_t
{ {
// check for strong content indicators // check for strong content indicators
if (!Q_strcasecmp(texname.data(), "origin")) { if (!Q_strcasecmp(texname.data(), "origin")) {
return create_extended_contents(CFLAGS_ORIGIN); return create_extended_contents({ true, false });
} else if (!Q_strcasecmp(texname.data(), "hint") || !Q_strcasecmp(texname.data(), "hintskip")) { } else if (!Q_strcasecmp(texname.data(), "hint") || !Q_strcasecmp(texname.data(), "hintskip")) {
return create_extended_contents(CFLAGS_HINT); return create_empty_contents();
} else if (!Q_strcasecmp(texname.data(), "clip")) { } else if (!Q_strcasecmp(texname.data(), "clip")) {
return create_extended_contents(CFLAGS_CLIP); return create_extended_contents({ false, true });
} else if (texname[0] == '*') { } else if (texname[0] == '*') {
if (!Q_strncasecmp(texname.data() + 1, "lava", 4)) { if (!Q_strncasecmp(texname.data() + 1, "lava", 4)) {
return create_liquid_contents(CONTENTS_LAVA); return create_liquid_contents(CONTENTS_LAVA);
@ -382,10 +445,10 @@ struct gamedef_q1_like_t : public gamedef_t
} }
} else if (!Q_strncasecmp(texname.data(), "sky", 3)) { } else if (!Q_strncasecmp(texname.data(), "sky", 3)) {
return create_sky_contents(); return create_sky_contents();
} else {
// and anything else is assumed to be a regular solid.
return create_solid_contents();
} }
// and anything else is assumed to be a regular solid.
return create_solid_contents();
} }
void init_filesystem(const fs::path &, const settings::common_settings &) const void init_filesystem(const fs::path &, const settings::common_settings &) const
@ -543,8 +606,9 @@ struct gamedef_q2_t : public gamedef_t
return c; return c;
} }
int32_t get_content_type(const contentflags_t &contents) const inline int32_t get_content_type(const contentflags_t &contents) const
{ {
// fixme-brushbsp: should TRANSLUCENT be here? does it need to be?
return contents.native & (Q2_ALL_VISIBLE_CONTENTS | return contents.native & (Q2_ALL_VISIBLE_CONTENTS |
(Q2_CONTENTS_PLAYERCLIP | Q2_CONTENTS_MONSTERCLIP | Q2_CONTENTS_ORIGIN | (Q2_CONTENTS_PLAYERCLIP | Q2_CONTENTS_MONSTERCLIP | Q2_CONTENTS_ORIGIN |
Q2_CONTENTS_TRANSLUCENT | Q2_CONTENTS_AREAPORTAL)); Q2_CONTENTS_TRANSLUCENT | Q2_CONTENTS_AREAPORTAL));
@ -579,8 +643,6 @@ struct gamedef_q2_t : public gamedef_t
return !!(contents.native & Q2_CONTENTS_SOLID); return !!(contents.native & Q2_CONTENTS_SOLID);
} }
contentflags_t create_extended_contents(const uint16_t &cflags) const { return {0, cflags}; }
contentflags_t create_empty_contents(const uint16_t &cflags) const { return {0, cflags}; } contentflags_t create_empty_contents(const uint16_t &cflags) const { return {0, cflags}; }
contentflags_t create_solid_contents(const uint16_t &cflags) const { return {Q2_CONTENTS_SOLID, cflags}; } contentflags_t create_solid_contents(const uint16_t &cflags) const { return {Q2_CONTENTS_SOLID, cflags}; }
@ -617,6 +679,18 @@ struct gamedef_q2_t : public gamedef_t
return result; return result;
} }
bool contents_are_type_equal(const contentflags_t &self, const contentflags_t &other) const
{
return (self.extended & CFLAGS_CONTENTS_MASK) == (other.extended & CFLAGS_CONTENTS_MASK) &&
get_content_type(self) == get_content_type(other);
}
bool contents_are_equal(const contentflags_t &self, const contentflags_t &other) const
{
return (self.extended & CFLAGS_CONTENTS_MASK) == (other.extended & CFLAGS_CONTENTS_MASK) &&
self.native == other.native;
}
bool contents_are_any_detail(const contentflags_t &contents) const bool contents_are_any_detail(const contentflags_t &contents) const
{ {
return ((contents.native & Q2_CONTENTS_DETAIL) != 0); return ((contents.native & Q2_CONTENTS_DETAIL) != 0);
@ -652,24 +726,40 @@ struct gamedef_q2_t : public gamedef_t
|| ((contents.native & mist2_type) == mist2_type); || ((contents.native & mist2_type) == mist2_type);
} }
bool contents_are_mirrored(const contentflags_t &contents) const
{
// if we have mirrorinside set, go ahead
if (contents.mirror_inside.has_value())
return contents.mirror_inside.value();
// Q2 is a bit different here. in vanilla tools,
// every content except SOLID is implicitly mirrorinside.
// the only exception is that 4bsp has the unused AUX
// contents to default to not mirroring the insides.
return !(contents.native & (Q2_CONTENTS_SOLID | Q2_CONTENTS_AUX));
}
bool contents_are_origin(const contentflags_t &contents) const
{
return contents.native & Q2_CONTENTS_ORIGIN;
}
bool contents_are_clip(const contentflags_t &contents) const
{
return contents.native & (Q2_CONTENTS_PLAYERCLIP | Q2_CONTENTS_MONSTERCLIP);
}
bool contents_clip_same_type(const contentflags_t &self, const contentflags_t &other) const
{
return (self.native & Q2_ALL_VISIBLE_CONTENTS) == (other.native & Q2_ALL_VISIBLE_CONTENTS) && self.clips_same_type.value_or(true);
}
bool contents_are_empty(const contentflags_t &contents) const bool contents_are_empty(const contentflags_t &contents) const
{ {
if (contents.extended & CFLAGS_CONTENTS_MASK) if (contents.extended & CFLAGS_CONTENTS_MASK)
return false; return false;
if (contents.native & Q2_CONTENTS_AREAPORTAL) return !get_content_type(contents);
return false; // HACK: needs to return false in order for LinkConvexFaces to assign Q2_CONTENTS_AREAPORTAL
// to the leaf
return !(contents.native & Q2_ALL_VISIBLE_CONTENTS);
}
bool contents_are_mirrored(const contentflags_t &contents) const
{
// fixme-brushbsp: support some way of opting out of mirrorinside
// If the brush is non-solid, mirror faces for the inside view
return !(contents.native & Q2_CONTENTS_SOLID);
} }
bool contents_are_solid(const contentflags_t &contents) const bool contents_are_solid(const contentflags_t &contents) const
@ -785,10 +875,8 @@ struct gamedef_q2_t : public gamedef_t
const std::string &texname, const surfflags_t &flags, const contentflags_t &contents) const const std::string &texname, const surfflags_t &flags, const contentflags_t &contents) const
{ {
// hints and skips are never detail, and have no content // hints and skips are never detail, and have no content
if (flags.native & Q2_SURF_HINT) { if (flags.native & (Q2_SURF_HINT | Q2_SURF_SKIP)) {
return {0, CFLAGS_HINT}; return {Q2_CONTENTS_EMPTY};
} else if (flags.native & Q2_SURF_SKIP) {
return {0, 0};
} }
contentflags_t surf_contents = contents; contentflags_t surf_contents = contents;
@ -801,7 +889,7 @@ struct gamedef_q2_t : public gamedef_t
// if we have TRANS33 or TRANS66, we have to be marked as WINDOW, // if we have TRANS33 or TRANS66, we have to be marked as WINDOW,
// so unset SOLID, give us WINDOW, and give us TRANSLUCENT // so unset SOLID, give us WINDOW, and give us TRANSLUCENT
if (flags.native & (Q2_SURF_TRANS33 | Q2_SURF_TRANS66)) { if (flags.native & (Q2_SURF_TRANS33 | Q2_SURF_TRANS66)) {
surf_contents.native |= Q2_CONTENTS_TRANSLUCENT | Q2_CONTENTS_DETAIL; surf_contents.native |= Q2_CONTENTS_TRANSLUCENT;
if (surf_contents.native & Q2_CONTENTS_SOLID) { if (surf_contents.native & Q2_CONTENTS_SOLID) {
surf_contents.native = (surf_contents.native & ~Q2_CONTENTS_SOLID) | Q2_CONTENTS_WINDOW; surf_contents.native = (surf_contents.native & ~Q2_CONTENTS_SOLID) | Q2_CONTENTS_WINDOW;
@ -809,21 +897,16 @@ struct gamedef_q2_t : public gamedef_t
} }
// translucent objects are automatically classified as detail // translucent objects are automatically classified as detail
if (surf_contents.native & Q2_CONTENTS_WINDOW) { if (surf_contents.native & Q2_CONTENTS_TRANSLUCENT) {
surf_contents.native |= Q2_CONTENTS_DETAIL; surf_contents.native |= Q2_CONTENTS_DETAIL;
} else if (surf_contents.native & (Q2_CONTENTS_MIST | Q2_CONTENTS_AUX)) { } else if (surf_contents.native & (Q2_CONTENTS_MIST | Q2_CONTENTS_AUX)) {
surf_contents.native |= Q2_CONTENTS_DETAIL; surf_contents.native |= Q2_CONTENTS_DETAIL;
} }
if (surf_contents.native & (Q2_CONTENTS_MONSTERCLIP | Q2_CONTENTS_PLAYERCLIP)) { if (surf_contents.native & (Q2_CONTENTS_MONSTERCLIP | Q2_CONTENTS_PLAYERCLIP)) {
surf_contents.extended |= CFLAGS_CLIP;
surf_contents.native |= Q2_CONTENTS_DETAIL; surf_contents.native |= Q2_CONTENTS_DETAIL;
} }
if (surf_contents.native & Q2_CONTENTS_ORIGIN) {
surf_contents.extended |= CFLAGS_ORIGIN;
}
return surf_contents; return surf_contents;
} }
@ -1105,10 +1188,16 @@ bool surfflags_t::is_valid(const gamedef_t *game) const
return game->surfflags_are_valid(*this); return game->surfflags_are_valid(*this);
} }
bool contentflags_t::equals(const gamedef_t *game, const contentflags_t &other) const
{
return game->contents_are_equal(*this, other) &&
mirror_inside == other.mirror_inside &&
clips_same_type == other.clips_same_type;
}
bool contentflags_t::types_equal(const contentflags_t &other, const gamedef_t *game) const bool contentflags_t::types_equal(const contentflags_t &other, const gamedef_t *game) const
{ {
return (extended & CFLAGS_CONTENTS_MASK) == (other.extended & CFLAGS_CONTENTS_MASK) && return game->contents_are_type_equal(*this, other);
game->get_content_type(*this) == game->get_content_type(other);
} }
int32_t contentflags_t::priority(const gamedef_t *game) const int32_t contentflags_t::priority(const gamedef_t *game) const
@ -1141,16 +1230,21 @@ bool contentflags_t::is_detail_illusionary(const gamedef_t *game) const
return game->contents_are_detail_illusionary(*this); return game->contents_are_detail_illusionary(*this);
} }
bool contentflags_t::is_empty(const gamedef_t *game) const
{
return game->contents_are_empty(*this);
}
bool contentflags_t::is_mirrored(const gamedef_t *game) const bool contentflags_t::is_mirrored(const gamedef_t *game) const
{ {
return game->contents_are_mirrored(*this); return game->contents_are_mirrored(*this);
} }
bool contentflags_t::will_clip_same_type(const gamedef_t *game, const contentflags_t &other) const
{
return game->contents_clip_same_type(*this, other);
}
bool contentflags_t::is_empty(const gamedef_t *game) const
{
return game->contents_are_empty(*this);
}
bool contentflags_t::is_solid(const gamedef_t *game) const bool contentflags_t::is_solid(const gamedef_t *game) const
{ {
return game->contents_are_solid(*this); return game->contents_are_solid(*this);
@ -1171,25 +1265,34 @@ bool contentflags_t::is_valid(const gamedef_t *game, bool strict) const
return game->contents_are_valid(*this, strict); return game->contents_are_valid(*this, strict);
} }
bool contentflags_t::is_clip(const gamedef_t *game) const
{
return game->contents_are_clip(*this);
}
bool contentflags_t::is_origin(const gamedef_t *game) const
{
return game->contents_are_origin(*this);
}
std::string contentflags_t::to_string(const gamedef_t *game) const std::string contentflags_t::to_string(const gamedef_t *game) const
{ {
std::string s = game->get_contents_display(*this); std::string s = game->get_contents_display(*this);
if (extended & CFLAGS_BMODEL_MIRROR_INSIDE) { // FIXME: how do we conditionally display this only when it matters (when it's not default basically)?
s += "|BMODEL_MIRROR_INSIDE"; s += fmt::format("|MIRROR_INSIDE[{}]", mirror_inside.has_value() ? (clips_same_type.value() ? "true" : "false") : "nullopt");
}
if (extended & CFLAGS_NO_CLIPPING_SAME_TYPE) { s += fmt::format("|CLIPS_SAME_TYPE[{}]", clips_same_type.has_value() ? (clips_same_type.value() ? "true" : "false") : "nullopt");
s += "|NO_CLIPPING_SAME_TYPE";
} // FIXME: duped for Q2, move to Q1?
if (extended & CFLAGS_HINT) { if (is_clip(game)) {
s += "|HINT";
}
if (extended & CFLAGS_CLIP) {
s += "|CLIP"; s += "|CLIP";
} }
if (extended & CFLAGS_ORIGIN) { if (is_origin(game)) {
s += "|ORIGIN"; s += "|ORIGIN";
} }
// FIXME: duped for Q2, move to Q1?
if (extended & CFLAGS_DETAIL) { if (extended & CFLAGS_DETAIL) {
s += "|DETAIL"; s += "|DETAIL";
} }

View File

@ -25,6 +25,7 @@
#include <variant> #include <variant>
#include <vector> #include <vector>
#include <unordered_map> #include <unordered_map>
#include <any>
#include <common/cmdlib.hh> #include <common/cmdlib.hh>
#include <common/log.hh> #include <common/log.hh>
@ -523,6 +524,7 @@ enum q1_contents_t : int32_t
// lower bits are stronger, and will eat weaker brushes completely // lower bits are stronger, and will eat weaker brushes completely
enum q2_contents_t : int32_t enum q2_contents_t : int32_t
{ {
Q2_CONTENTS_EMPTY = 0,
Q2_CONTENTS_SOLID = nth_bit(0), // an eye is never valid in a solid Q2_CONTENTS_SOLID = nth_bit(0), // an eye is never valid in a solid
Q2_CONTENTS_WINDOW = nth_bit(1), // translucent, but not watery Q2_CONTENTS_WINDOW = nth_bit(1), // translucent, but not watery
Q2_CONTENTS_AUX = nth_bit(2), Q2_CONTENTS_AUX = nth_bit(2),
@ -563,12 +565,7 @@ enum q2_contents_t : int32_t
// Special contents flags for the compiler only // Special contents flags for the compiler only
enum extended_cflags_t : uint16_t enum extended_cflags_t : uint16_t
{ {
CFLAGS_BMODEL_MIRROR_INSIDE = nth_bit(3), /* set "_mirrorinside" "1" on a bmodel to mirror faces for when the player is inside. */
CFLAGS_NO_CLIPPING_SAME_TYPE = nth_bit(4), /* Don't clip the same content type. mostly intended for CONTENTS_DETAIL_ILLUSIONARY */
// only one of these flags below should ever be set. // only one of these flags below should ever be set.
CFLAGS_HINT = nth_bit(5),
CFLAGS_CLIP = nth_bit(6),
CFLAGS_ORIGIN = nth_bit(7),
CFLAGS_DETAIL = nth_bit(8), CFLAGS_DETAIL = nth_bit(8),
CFLAGS_DETAIL_ILLUSIONARY = nth_bit(9), CFLAGS_DETAIL_ILLUSIONARY = nth_bit(9),
CFLAGS_DETAIL_FENCE = nth_bit(10), CFLAGS_DETAIL_FENCE = nth_bit(10),
@ -577,7 +574,7 @@ enum extended_cflags_t : uint16_t
CFLAGS_DETAIL_MASK = (CFLAGS_DETAIL | CFLAGS_DETAIL_ILLUSIONARY | CFLAGS_DETAIL_FENCE), CFLAGS_DETAIL_MASK = (CFLAGS_DETAIL | CFLAGS_DETAIL_ILLUSIONARY | CFLAGS_DETAIL_FENCE),
// all of the special content types // all of the special content types
CFLAGS_CONTENTS_MASK = CFLAGS_CONTENTS_MASK =
(CFLAGS_HINT | CFLAGS_CLIP | CFLAGS_ORIGIN | CFLAGS_DETAIL_MASK | CFLAGS_ILLUSIONARY_VISBLOCKER) (CFLAGS_DETAIL_MASK | CFLAGS_ILLUSIONARY_VISBLOCKER)
}; };
struct gamedef_t; struct gamedef_t;
@ -590,25 +587,36 @@ struct contentflags_t
// extra flags, specific to BSP only // extra flags, specific to BSP only
uint16_t extended; uint16_t extended;
constexpr bool operator==(const contentflags_t &other) const // extra data supplied by the game
{ std::any game_data;
return native == other.native && extended == other.extended;
}
constexpr bool operator!=(const contentflags_t &other) const { return !(*this == other); } // 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;
bool equals(const gamedef_t *game, const contentflags_t &other) const;
// is any kind of detail? (solid, liquid, etc.) // is any kind of detail? (solid, liquid, etc.)
bool is_any_detail(const gamedef_t *game) const; bool is_any_detail(const gamedef_t *game) const;
bool is_detail_solid(const gamedef_t *game) const; bool is_detail_solid(const gamedef_t *game) const;
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;
bool is_mirrored(const gamedef_t *game) const;
contentflags_t &set_mirrored(const std::optional<bool> &mirror_inside_value) { mirror_inside = mirror_inside_value; return *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;
contentflags_t &set_clips_same_type(const std::optional<bool> &clips_same_type_value) { clips_same_type = clips_same_type_value; return *this; }
bool is_empty(const gamedef_t *game) const; bool is_empty(const gamedef_t *game) const;
bool is_mirrored(const gamedef_t *game) const;
// detail solid or structural solid // detail solid or structural solid
bool is_any_solid(const gamedef_t *game) const { inline bool is_any_solid(const gamedef_t *game) const {
return is_solid(game) return is_solid(game)
|| is_detail_solid(game); || is_detail_solid(game);
} }
@ -618,16 +626,10 @@ struct contentflags_t
bool is_sky(const gamedef_t *game) const; bool is_sky(const gamedef_t *game) const;
bool is_liquid(const gamedef_t *game) const; bool is_liquid(const gamedef_t *game) const;
bool is_valid(const gamedef_t *game, bool strict = true) const; bool is_valid(const gamedef_t *game, bool strict = true) const;
bool is_clip(const gamedef_t *game) const;
bool is_origin(const gamedef_t *game) const;
constexpr bool is_hint() const { return extended & CFLAGS_HINT; } inline bool is_fence(const gamedef_t *game) const {
constexpr bool is_clip() const { return extended & CFLAGS_CLIP; }
constexpr bool is_origin() const { return extended & CFLAGS_ORIGIN; }
constexpr bool clips_same_type() const { return !(extended & CFLAGS_NO_CLIPPING_SAME_TYPE); }
bool is_fence(const gamedef_t *game) const {
return is_detail_fence(game) || is_detail_illusionary(game); return is_detail_fence(game) || is_detail_illusionary(game);
} }
@ -1801,10 +1803,8 @@ struct gamedef_t
// 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 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 int32_t get_content_type(const contentflags_t &contents) const = 0;
virtual int32_t contents_priority(const contentflags_t &contents) const = 0; virtual int32_t contents_priority(const contentflags_t &contents) const = 0;
virtual bool chops(const contentflags_t &) const = 0; virtual bool chops(const contentflags_t &) const = 0;
virtual contentflags_t create_extended_contents(const uint16_t &cflags = 0) const = 0;
virtual contentflags_t create_empty_contents(const uint16_t &cflags = 0) const = 0; virtual contentflags_t create_empty_contents(const uint16_t &cflags = 0) const = 0;
virtual contentflags_t create_solid_contents(const uint16_t &cflags = 0) const = 0; virtual contentflags_t create_solid_contents(const uint16_t &cflags = 0) const = 0;
virtual contentflags_t create_sky_contents(const uint16_t &cflags = 0) const = 0; virtual contentflags_t create_sky_contents(const uint16_t &cflags = 0) const = 0;
@ -1812,12 +1812,17 @@ struct gamedef_t
virtual contentflags_t create_detail_illusionary_contents(const contentflags_t &original) const = 0; virtual contentflags_t create_detail_illusionary_contents(const contentflags_t &original) const = 0;
virtual contentflags_t create_detail_fence_contents(const contentflags_t &original) const = 0; virtual contentflags_t create_detail_fence_contents(const contentflags_t &original) const = 0;
virtual contentflags_t create_detail_solid_contents(const contentflags_t &original) const = 0; virtual contentflags_t create_detail_solid_contents(const contentflags_t &original) const = 0;
virtual bool contents_are_type_equal(const contentflags_t &self, const contentflags_t &other) const = 0;
virtual bool contents_are_equal(const contentflags_t &self, const contentflags_t &other) const = 0;
virtual bool contents_are_any_detail(const contentflags_t &contents) const = 0; virtual bool contents_are_any_detail(const contentflags_t &contents) const = 0;
virtual bool contents_are_detail_solid(const contentflags_t &contents) const = 0; virtual bool contents_are_detail_solid(const contentflags_t &contents) const = 0;
virtual bool contents_are_detail_fence(const contentflags_t &contents) const = 0; virtual bool contents_are_detail_fence(const contentflags_t &contents) const = 0;
virtual bool contents_are_detail_illusionary(const contentflags_t &contents) const = 0; virtual bool contents_are_detail_illusionary(const contentflags_t &contents) const = 0;
virtual bool contents_are_empty(const contentflags_t &contents) const = 0;
virtual bool contents_are_mirrored(const contentflags_t &contents) const = 0; virtual bool contents_are_mirrored(const contentflags_t &contents) const = 0;
virtual bool contents_are_origin(const contentflags_t &contents) const = 0;
virtual bool contents_are_clip(const contentflags_t &contents) const = 0;
virtual bool contents_are_empty(const contentflags_t &contents) const = 0;
virtual bool contents_clip_same_type(const contentflags_t &self, const contentflags_t &other) const = 0;
virtual bool contents_are_solid(const contentflags_t &contents) const = 0; virtual bool contents_are_solid(const contentflags_t &contents) const = 0;
virtual bool contents_are_sky(const contentflags_t &contents) const = 0; virtual bool contents_are_sky(const contentflags_t &contents) const = 0;
virtual bool contents_are_liquid(const contentflags_t &contents) const = 0; virtual bool contents_are_liquid(const contentflags_t &contents) const = 0;

View File

@ -1134,10 +1134,6 @@ struct twosided
// swap the front and back values // swap the front and back values
constexpr void swap() { std::swap(front, back); } constexpr void swap() { std::swap(front, back); }
// equality checks
constexpr bool operator==(const twosided &other) const { return front == other.front && back == other.back; }
constexpr bool operator!=(const twosided &other) const { return !(*this == other); }
}; };
namespace qv { namespace qv {

View File

@ -333,6 +333,25 @@ void FixRotateOrigin(mapentity_t *entity)
SetKeyValue(entity, "origin", qv::to_string(offset).c_str()); SetKeyValue(entity, "origin", qv::to_string(offset).c_str());
} }
static bool Brush_IsHint(const hullbrush_t &brush)
{
for (auto &f : brush.faces)
if (f.flags.is_hint)
return true;
return false;
}
static bool MapBrush_IsHint(const mapbrush_t &brush)
{
for (size_t i = 0; i < brush.numfaces; i++)
if (brush.face(i).flags.is_hint)
return true;
return false;
}
/* /*
================= =================
CreateBrushFaces CreateBrushFaces
@ -354,7 +373,7 @@ static std::vector<face_t> CreateBrushFaces(const mapentity_t *src, hullbrush_t
hullbrush->bounds = {}; hullbrush->bounds = {};
for (auto &mapface : hullbrush->faces) { for (auto &mapface : hullbrush->faces) {
if (hullnum <= 0 && hullbrush->contents.is_hint()) { if (hullnum <= 0 && Brush_IsHint(*hullbrush)) {
/* Don't generate hintskip faces */ /* Don't generate hintskip faces */
const mtexinfo_t &texinfo = map.mtexinfos.at(mapface.texinfo); const mtexinfo_t &texinfo = map.mtexinfos.at(mapface.texinfo);
@ -802,7 +821,7 @@ static void Brush_LoadEntity(mapentity_t *dst, const mapentity_t *src, const int
for (int i = 0; i < src->nummapbrushes; i++) { for (int i = 0; i < src->nummapbrushes; i++) {
const mapbrush_t *mapbrush = &src->mapbrush(i); const mapbrush_t *mapbrush = &src->mapbrush(i);
const contentflags_t contents = Brush_GetContents(mapbrush); const contentflags_t contents = Brush_GetContents(mapbrush);
if (contents.is_origin()) { if (contents.is_origin(options.target_game)) {
if (dst == map.world_entity()) { if (dst == map.world_entity()) {
logging::print("WARNING: Ignoring origin brush in worldspawn\n"); logging::print("WARNING: Ignoring origin brush in worldspawn\n");
continue; continue;
@ -859,10 +878,11 @@ static void Brush_LoadEntity(mapentity_t *dst, const mapentity_t *src, const int
/* _mirrorinside key (for func_water etc.) */ /* _mirrorinside key (for func_water etc.) */
const bool mirrorinside_set = *ValueForKey(src, "_mirrorinside"); const bool mirrorinside_set = *ValueForKey(src, "_mirrorinside");
const bool all_mirrorinside = !!atoi(ValueForKey(src, "_mirrorinside")); const std::optional<bool> mirrorinside = mirrorinside_set ? decltype(mirrorinside)(atoi(ValueForKey(src, "_mirrorinside")) ? true : false) : std::nullopt;
/* _noclipfaces */ /* _noclipfaces */
const bool noclipfaces = !!atoi(ValueForKey(src, "_noclipfaces")); const bool noclipfaces_set = *ValueForKey(src, "_noclipfaces");
const std::optional<bool> clipsametype = noclipfaces_set ? decltype(clipsametype)(atoi(ValueForKey(src, "_noclipfaces")) ? false : true) : std::nullopt;
const bool func_illusionary_visblocker = (0 == Q_strcasecmp(classname, "func_illusionary_visblocker")); const bool func_illusionary_visblocker = (0 == Q_strcasecmp(classname, "func_illusionary_visblocker"));
@ -880,21 +900,14 @@ static void Brush_LoadEntity(mapentity_t *dst, const mapentity_t *src, const int
bool detail = false; bool detail = false;
bool detail_illusionary = false; bool detail_illusionary = false;
bool detail_fence = false; bool detail_fence = false;
bool mirrorinside = all_mirrorinside;
// inherit the per-entity settings // inherit the per-entity settings
detail |= all_detail; detail |= all_detail;
detail_illusionary |= all_detail_illusionary; detail_illusionary |= all_detail_illusionary;
detail_fence |= all_detail_fence; detail_fence |= all_detail_fence;
if (!mirrorinside_set) {
if (options.target_game->id == GAME_QUAKE_II && (contents.native & (Q2_CONTENTS_AUX | Q2_CONTENTS_MIST))) {
mirrorinside = true;
}
}
/* "origin" brushes always discarded */ /* "origin" brushes always discarded */
if (contents.is_origin()) if (contents.is_origin(options.target_game))
continue; continue;
/* -omitdetail option omits all types of detail */ /* -omitdetail option omits all types of detail */
@ -927,7 +940,7 @@ static void Brush_LoadEntity(mapentity_t *dst, const mapentity_t *src, const int
* include them in the model bounds so collision detection works * include them in the model bounds so collision detection works
* correctly. * correctly.
*/ */
if (contents.is_clip() && hullnum != HULL_COLLISION) { if (hullnum != HULL_COLLISION && contents.is_clip(options.target_game)) {
if (hullnum == 0) { if (hullnum == 0) {
std::optional<brush_t> brush = LoadBrush(src, mapbrush, contents, rotate_offset, rottype, hullnum); std::optional<brush_t> brush = LoadBrush(src, mapbrush, contents, rotate_offset, rottype, hullnum);
@ -943,7 +956,7 @@ static void Brush_LoadEntity(mapentity_t *dst, const mapentity_t *src, const int
} }
/* "hint" brushes don't affect the collision hulls */ /* "hint" brushes don't affect the collision hulls */
if (contents.is_hint()) { if (MapBrush_IsHint(*mapbrush)) {
if (hullnum > 0) if (hullnum > 0)
continue; continue;
contents = options.target_game->create_empty_contents(); contents = options.target_game->create_empty_contents();
@ -975,12 +988,8 @@ static void Brush_LoadEntity(mapentity_t *dst, const mapentity_t *src, const int
contents = options.target_game->create_solid_contents(); contents = options.target_game->create_solid_contents();
// apply extended flags // apply extended flags
if (mirrorinside) { contents.set_mirrored(mirrorinside);
contents.extended |= CFLAGS_BMODEL_MIRROR_INSIDE; contents.set_clips_same_type(clipsametype);
}
if (noclipfaces) {
contents.extended |= CFLAGS_NO_CLIPPING_SAME_TYPE;
}
if (func_illusionary_visblocker) { if (func_illusionary_visblocker) {
contents.extended |= CFLAGS_ILLUSIONARY_VISBLOCKER; contents.extended |= CFLAGS_ILLUSIONARY_VISBLOCKER;
} }

View File

@ -262,7 +262,7 @@ static bool ShouldClipbrushEatBrush(const brush_t &brush, const brush_t &clipbru
} }
if (clipbrush.contents.types_equal(brush.contents, options.target_game)) { if (clipbrush.contents.types_equal(brush.contents, options.target_game)) {
return clipbrush.contents.clips_same_type(); return clipbrush.contents.will_clip_same_type(options.target_game);
} }
return false; return false;
@ -426,7 +426,7 @@ Returns a >= b as far as brush clipping
bool BrushGE(const brush_t& a, const brush_t& b) bool BrushGE(const brush_t& a, const brush_t& b)
{ {
// same contents clip each other // same contents clip each other
if (a.contents == b.contents && a.contents.clips_same_type()) { if (a.contents.will_clip_same_type(options.target_game, b.contents)) {
// map file order // map file order
return a.file_order > b.file_order; return a.file_order > b.file_order;
} }

View File

@ -71,7 +71,8 @@ static void ExportObjFace(std::ofstream &f, const face_t *face, int *vertcount)
// not sure why -v is needed, .obj uses (0, 0) in the top left apparently? // not sure why -v is needed, .obj uses (0, 0) in the top left apparently?
fmt::print(f, "vt {:.9} {:.9}\n", uv[0], -uv[1]); fmt::print(f, "vt {:.9} {:.9}\n", uv[0], -uv[1]);
} }
// fixme-brushbsp
fmt::print(f, "usemtl contents{}_{}\n", face->contents[0].native, face->contents[0].extended); fmt::print(f, "usemtl contents{}_{}\n", face->contents[0].native, face->contents[0].extended);
f << 'f'; f << 'f';
for (int i = 0; i < face->w.size(); i++) { for (int i = 0; i < face->w.size(); i++) {
@ -108,8 +109,9 @@ void ExportObj_Faces(const std::string &filesuffix, const std::vector<const face
WriteContentsMaterial(mtlfile, {CONTENTS_LAVA}, 0.2, 0.0, 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);
WriteContentsMaterial(mtlfile, {CONTENTS_SOLID, CFLAGS_CLIP}, 1, 0.8, 0.8); // fixme-brushbsp
WriteContentsMaterial(mtlfile, {CONTENTS_EMPTY, CFLAGS_HINT}, 1, 1, 1); //WriteContentsMaterial(mtlfile, {CONTENTS_SOLID, CFLAGS_CLIP}, 1, 0.8, 0.8);
//WriteContentsMaterial(mtlfile, {CONTENTS_EMPTY, CFLAGS_HINT}, 1, 1, 1);
WriteContentsMaterial(mtlfile, {CONTENTS_SOLID, CFLAGS_DETAIL}, 0.5, 0.5, 0.5); WriteContentsMaterial(mtlfile, {CONTENTS_SOLID, CFLAGS_DETAIL}, 0.5, 0.5, 0.5);

View File

@ -69,7 +69,8 @@ static face_t *TryMerge(face_t *f1, face_t *f2)
bool keep1, keep2; bool keep1, keep2;
if (!f1->w.size() || !f2->w.size() || f1->planeside != f2->planeside || f1->texinfo != f2->texinfo || if (!f1->w.size() || !f2->w.size() || f1->planeside != f2->planeside || f1->texinfo != f2->texinfo ||
f1->contents != f2->contents || f1->lmshift != f2->lmshift) !f1->contents[0].equals(options.target_game, f2->contents[0]) || !f1->contents[1].equals(options.target_game, f2->contents[1]) ||
f1->lmshift[0] != f2->lmshift[0] || f1->lmshift[1] != f2->lmshift[1])
return NULL; return NULL;
// find a common edge // find a common edge

View File

@ -998,7 +998,7 @@ static void BSPX_Brushes_AddModel(
case CONTENTS_SLIME: case CONTENTS_SLIME:
case CONTENTS_LAVA: case CONTENTS_LAVA:
case CONTENTS_SKY: case CONTENTS_SKY:
if (b->contents.is_clip()) { if (b->contents.is_clip(options.target_game)) {
perbrush.contents = -8; perbrush.contents = -8;
} else { } else {
perbrush.contents = b->contents.native; perbrush.contents = b->contents.native;
@ -1008,7 +1008,7 @@ static void BSPX_Brushes_AddModel(
// perbrush.contents = -16; // perbrush.contents = -16;
// break; // break;
default: { default: {
if (b->contents.is_clip()) { if (b->contents.is_clip(options.target_game)) {
perbrush.contents = -8; perbrush.contents = -8;
} else { } else {
logging::print("WARNING: Unknown contents: {}. Translating to solid.\n", logging::print("WARNING: Unknown contents: {}. Translating to solid.\n",

View File

@ -574,11 +574,11 @@ static std::list<face_t *> ClipFacesToTree_r(node_t *node, const brush_t *srcbru
return {}; return {};
} }
// translucent contents also clip faces // see what the game thinks about the clip
if (node->contents == srcbrush->contents if (srcbrush->contents.will_clip_same_type(options.target_game, node->contents)) {
&& srcbrush->contents.clips_same_type()) {
return {}; return {};
} }
// other content types let the faces thorugh // other content types let the faces thorugh
return faces; return faces;
} }