ericw-tools/include/common/bspfile_common.hh

540 lines
20 KiB
C++

/* Copyright (C) 1996-1997 Id Software, Inc.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
See file, 'COPYING', for details.
*/
#pragma once
#include <cstdint>
#include <array>
#include <tuple>
#include <variant>
#include <vector>
#include <unordered_map>
#include <any>
#include <optional>
#include <span>
#include <common/bitflags.hh>
#include <common/fs.hh>
#include <common/qvec.hh>
#include <common/aabb.hh>
namespace settings
{
class common_settings;
}
struct lump_t
{
int32_t fileofs;
int32_t filelen;
// serialize for streams
void stream_write(std::ostream &s) const;
void stream_read(std::istream &s);
};
using contents_int_t = uint64_t;
/**
* Superset of Q1- and Q2- features, plus EWT extensions.
*
* Update bitflag_names if this is changed.
*/
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)
/**
* Visblocking mist, but doesn't merge with mist/aux
*/
EWT_VISCONTENTS_ILLUSIONARY_VISBLOCKER = nth_bit<uint64_t>(4),
/**
* Mist but not mirrored by default. Doesn't merge with mist. Never visblocking / always detail.
*/
EWT_VISCONTENTS_AUX = nth_bit<uint64_t>(5),
EWT_VISCONTENTS_LAVA = nth_bit<uint64_t>(6),
EWT_VISCONTENTS_SLIME = nth_bit<uint64_t>(7),
EWT_VISCONTENTS_WATER = nth_bit<uint64_t>(8),
/**
* Never visblocking / always detail.
*/
EWT_VISCONTENTS_MIST = nth_bit<uint64_t>(9),
EWT_LAST_VISIBLE_CONTENTS_INDEX = 9,
EWT_LAST_VISIBLE_CONTENTS = EWT_VISCONTENTS_MIST,
EWT_INVISCONTENTS_ORIGIN = nth_bit<uint64_t>(10), // removed before bsping an entity
EWT_INVISCONTENTS_PLAYERCLIP = nth_bit<uint64_t>(11), // Q1 clip
EWT_INVISCONTENTS_MONSTERCLIP = nth_bit<uint64_t>(12),
EWT_INVISCONTENTS_AREAPORTAL = nth_bit<uint64_t>(13),
EWT_INVISCONTENTS_NO_WATERJUMP = nth_bit<uint64_t>(14), // re-release
EWT_INVISCONTENTS_PROJECTILECLIP = nth_bit<uint64_t>(15), // re-release
EWT_CFLAG_MIRROR_INSIDE = nth_bit<uint64_t>(16),
EWT_CFLAG_MIRROR_INSIDE_SET = nth_bit<uint64_t>(17),
EWT_CFLAG_SUPPRESS_CLIPPING_SAME_TYPE = nth_bit<uint64_t>(18),
EWT_CFLAG_CURRENT_0 = nth_bit<uint64_t>(19),
EWT_CFLAG_CURRENT_90 = nth_bit<uint64_t>(20),
EWT_CFLAG_CURRENT_180 = nth_bit<uint64_t>(21),
EWT_CFLAG_CURRENT_270 = nth_bit<uint64_t>(22),
EWT_CFLAG_CURRENT_UP = nth_bit<uint64_t>(23),
EWT_CFLAG_CURRENT_DOWN = nth_bit<uint64_t>(24),
EWT_CFLAG_TRANSLUCENT = nth_bit<uint64_t>(25), // auto set if any surface has trans,
EWT_CFLAG_LADDER = nth_bit<uint64_t>(26),
EWT_CFLAG_MONSTER = nth_bit<uint64_t>(27), // disallowed in maps, only for gamecode use
EWT_CFLAG_DEADMONSTER = nth_bit<uint64_t>(28), // disallowed in maps, only for gamecode use
EWT_CFLAG_DETAIL = nth_bit<uint64_t>(29), // brushes to be added after vis leafs
// 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>(30),
EWT_CFLAG_Q2_UNUSED_8 = nth_bit<uint64_t>(31),
EWT_CFLAG_Q2_UNUSED_9 = nth_bit<uint64_t>(32),
EWT_CFLAG_Q2_UNUSED_10 = nth_bit<uint64_t>(33),
EWT_CFLAG_Q2_UNUSED_11 = nth_bit<uint64_t>(34),
EWT_CFLAG_Q2_UNUSED_12 = 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),
// masks
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_ILLUSIONARY_VISBLOCKER | EWT_VISCONTENTS_AUX |
EWT_VISCONTENTS_LAVA | EWT_VISCONTENTS_SLIME | EWT_VISCONTENTS_WATER |
EWT_VISCONTENTS_MIST,
EWT_ALL_INVISCONTENTS = EWT_INVISCONTENTS_ORIGIN | EWT_INVISCONTENTS_PLAYERCLIP | EWT_INVISCONTENTS_MONSTERCLIP |
EWT_INVISCONTENTS_AREAPORTAL | EWT_INVISCONTENTS_PROJECTILECLIP,
};
struct gamedef_t;
struct contentflags_t
{
contents_t flags;
static contentflags_t make(contents_int_t f) { return contentflags_t{.flags = static_cast<contents_t>(f)}; }
bool equals(const gamedef_t *game, contentflags_t other) const;
// is any kind of detail? (solid, liquid, etc.)
bool is_any_detail(const gamedef_t *game) const;
bool is_detail_solid(const gamedef_t *game) const;
bool is_detail_wall(const gamedef_t *game) const;
bool is_detail_fence(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);
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, 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);
bool is_empty(const gamedef_t *game) const;
bool is_any_solid(const gamedef_t *game) const;
// solid, not detail or any other extended content types
bool is_solid(const gamedef_t *game) const;
bool has_structural_solid() const { return (flags & EWT_VISCONTENTS_SOLID) && !(flags & EWT_CFLAG_DETAIL); }
bool is_sky(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_clip(const gamedef_t *game) const;
bool is_origin(const gamedef_t *game) const;
void make_valid(const gamedef_t *game);
bool is_fence(const gamedef_t *game) const;
// check if this content's `type` - which is distinct from various
// flags that turn things on/off - match. Exactly what the native
// "type" is depends on the game, but any of the detail flags must
// also match.
bool types_equal(contentflags_t other, const gamedef_t *game) const;
// when multiple brushes contribute to a leaf, the higher priority
// one determines the leaf contents
int32_t priority(const gamedef_t *game) const;
// whether this should chop (if so, only lower priority content brushes get chopped)
// should return true only for solid / opaque content types
bool chops(const gamedef_t *game) const;
std::string to_string() 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;
}
}
if (flags & EWT_INVISCONTENTS_PLAYERCLIP) {
return 10;
} else if (flags & EWT_INVISCONTENTS_MONSTERCLIP) {
return 11;
} else if (flags & EWT_INVISCONTENTS_PROJECTILECLIP) {
return 14;
}
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);
}
};
enum q1_surf_flags_t : int32_t;
enum q2_surf_flags_t : int32_t;
struct surfflags_t
{
// native flags value; what's written to the BSP for a Q2 map basically
// when compiling Q1 maps, we can use these internally but obviously not write them out
q2_surf_flags_t native_q2;
// native q1 flags
q1_surf_flags_t native_q1;
// an invisible surface (Q1 "skip" texture, Q2 SURF_NODRAW)
bool is_nodraw() const;
void set_nodraw(bool nodraw);
// hint surface
bool is_hint() const;
void set_hint(bool hint);
// is a skip surface from a hint brush
bool is_hintskip() const;
void set_hintskip(bool hintskip);
// don't receive dirtmapping
bool no_dirt : 1;
// don't cast a shadow
bool no_shadow : 1;
// light doesn't bounce off this face
bool no_bounce : 1;
// opt out of minlight on this face (including opting out of local minlight, so
// not the same as just setting minlight to 0).
bool no_minlight : 1;
// don't expand this face for larger clip hulls
bool no_expand : 1;
// this face doesn't receive light
bool light_ignore : 1;
// if true, rescales any surface light emitted by these brushes to emit 50% light at 90 degrees from the surface
// normal if false, use a more natural angle falloff of 0% at 90 degrees
std::optional<bool> surflight_rescale;
// override surface lighting style
std::optional<int32_t> surflight_style;
// override surface lighting targetname
std::optional<std::string> surflight_targetname;
// override the textures' surflight color
std::optional<qvec3b> surflight_color;
// surface light rescaling
std::optional<float> surflight_minlight_scale;
// surface light attenuation
std::optional<float> surflight_atten;
// if non zero, enables phong shading and gives the angle threshold to use
float phong_angle;
// if non zero, overrides _phong_angle for concave joints
float phong_angle_concave;
// _phong_group key, equivalent q2 map format's use of the "value" field
int phong_group;
// minlight value for this face. empty = inherit from worldspawn.
std::optional<float> minlight;
// red minlight colors for this face
qvec3b minlight_color;
// custom opacity
std::optional<float> light_alpha;
// two-sided lighting
std::optional<bool> light_twosided;
// maxlight value for this face
float maxlight;
// light color scale
float lightcolorscale = 1.0;
// surface light group
int32_t surflight_group;
// custom world_units_per_luxel for this geometry
std::optional<float> world_units_per_luxel;
std::optional<int32_t> object_channel_mask;
bool needs_write() const;
public:
// sort support
auto operator<=>(const surfflags_t &other) const = default;
bool is_valid(const gamedef_t *game) const;
};
// native game target ID
enum gameid_t
{
GAME_UNKNOWN,
GAME_QUAKE,
GAME_HEXEN_II,
GAME_HALF_LIFE,
GAME_QUAKE_II,
GAME_TOTAL
};
struct content_stats_base_t
{
virtual ~content_stats_base_t() = default;
};
// Game definition, which contains data specific to
// the game a BSP version is being compiled for.
struct gamedef_t
{
// friendly name, used for commands
const char *friendly_name;
// ID, used for quick comparisons
gameid_t id = GAME_UNKNOWN;
// whether the game uses an RGB lightmap or not
bool has_rgb_lightmap = false;
// whether the game supports content flags on brush models
bool allow_contented_bmodels = false;
// base dir for searching for paths, in case we are in a mod dir
// note: we need this to be able to be overridden via options
const std::string default_base_dir = {};
// max values of entity key & value pairs, only used for
// printing warnings.
size_t max_entity_key = 32;
size_t max_entity_value = 128;
gamedef_t(const char *friendly_name, const char *default_base_dir);
// surface stores lightmap/luxel color data
virtual bool surf_is_lightmapped(
const surfflags_t &flags, const char *texname, bool light_nodraw, bool lightgrid_enabled) const = 0;
// surface can be emissive
virtual bool surf_is_emissive(const surfflags_t &flags, const char *texname) const = 0;
virtual bool surf_is_subdivided(const surfflags_t &flags) const = 0;
virtual bool surfflags_are_valid(const surfflags_t &flags) const = 0;
/**
* We block certain surface flag combinations from ever smoothing together
* e.g. warping and non-warping
*/
virtual bool surfflags_may_phong(const surfflags_t &a, const surfflags_t &b) const = 0;
virtual int32_t surfflags_from_string(std::string_view str) const = 0;
// 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 contentflags_t create_contents_from_native(int32_t native) const = 0;
virtual int32_t contents_to_native(contentflags_t contents) const = 0;
virtual contentflags_t cluster_contents(contentflags_t contents0, contentflags_t contents1) const = 0;
virtual contentflags_t create_empty_contents() const = 0;
virtual contentflags_t create_solid_contents() const = 0;
virtual contentflags_t create_detail_illusionary_contents(contentflags_t original) const = 0;
virtual contentflags_t create_detail_fence_contents(contentflags_t original) const = 0;
virtual contentflags_t create_detail_wall_contents(contentflags_t original) const = 0;
virtual contentflags_t create_detail_solid_contents(contentflags_t original) const = 0;
virtual contentflags_t clear_detail(contentflags_t original) const = 0;
virtual contentflags_t set_detail(contentflags_t original) const = 0;
virtual bool contents_are_type_equal(contentflags_t self, contentflags_t other) const = 0;
virtual bool contents_are_equal(contentflags_t self, contentflags_t other) const = 0;
virtual bool contents_are_any_detail(contentflags_t contents) const = 0;
virtual bool contents_are_detail_solid(contentflags_t contents) const = 0;
virtual bool contents_are_detail_wall(contentflags_t contents) const = 0;
virtual bool contents_are_detail_fence(contentflags_t contents) const = 0;
virtual bool contents_are_detail_illusionary(contentflags_t contents) const = 0;
virtual bool contents_are_origin(contentflags_t contents) const = 0;
virtual bool contents_are_clip(contentflags_t contents) const = 0;
virtual bool contents_are_empty(contentflags_t contents) const = 0;
virtual bool contents_clip_same_type(contentflags_t self, contentflags_t other) const = 0;
virtual bool contents_are_any_solid(contentflags_t contents) const = 0;
virtual bool contents_are_solid(contentflags_t contents) const = 0;
virtual bool contents_are_sky(contentflags_t contents) const = 0;
virtual bool contents_are_liquid(contentflags_t contents) const = 0;
virtual bool contents_are_valid(contentflags_t contents, bool strict = true) const = 0;
virtual int32_t contents_from_string(std::string_view str) const = 0;
virtual bool portal_can_see_through(contentflags_t contents0, contentflags_t contents1, bool transwater) const = 0;
virtual bool contents_seals_map(contentflags_t contents) const = 0;
virtual bool contents_are_opaque(contentflags_t contents, bool transwater) const = 0;
enum class remap_type_t
{
brush,
leaf
};
virtual contentflags_t contents_remap_for_export(contentflags_t contents, remap_type_t type) const = 0;
virtual contentflags_t combine_contents(contentflags_t a, contentflags_t b) const = 0;
// for a portal with contents from `a` to `b`, returns what type of face should be rendered facing `a` and `b`
virtual contentflags_t portal_visible_contents(contentflags_t a, contentflags_t b) const = 0;
// for a brush with the given contents touching a portal with the required `portal_visible_contents`, as determined
// by portal_visible_contents, should the `brushside_side` of the brushside generate a face? e.g. liquids generate
// front and back sides by default, but for q1 detail_wall/detail_illusionary the back side is opt-in with
// _mirrorinside
virtual bool portal_generates_face(
contentflags_t portal_visible_contents, contentflags_t brushcontents, planeside_t brushside_side) const = 0;
virtual void contents_make_valid(contentflags_t &contents) const = 0;
virtual std::span<const aabb3d> get_hull_sizes() const = 0;
virtual contentflags_t face_get_contents(
const std::string &texname, const surfflags_t &flags, contentflags_t contents) const = 0;
virtual void init_filesystem(const fs::path &source, const settings::common_settings &settings) const = 0;
virtual const std::vector<qvec3b> &get_default_palette() const = 0;
virtual std::unique_ptr<content_stats_base_t> create_content_stats() const = 0;
virtual void count_contents_in_stats(contentflags_t contents, content_stats_base_t &stats) const = 0;
virtual void print_content_stats(const content_stats_base_t &stats, const char *what) const = 0;
};
// Lump specification; stores the name and size
// of an individual entry in the lump. Count is
// calculated as (lump_size / size)
struct lumpspec_t
{
const char *name;
size_t size;
};
// BSP version struct & instances
struct bspversion_t
{
/* identifier value, the first int32_t in the header */
int32_t ident;
/* version value, if supported */
std::optional<int32_t> version;
/* short name used for command line args, etc */
const char *short_name;
/* full display name for printing */
const char *name;
/* lump specification */
const std::initializer_list<lumpspec_t> lumps;
/* game ptr */
const gamedef_t *game;
/* if we surpass the limits of this format, upgrade to this one */
const bspversion_t *extended_limits;
};
// FMT support
template<>
struct fmt::formatter<bspversion_t>
{
constexpr auto parse(format_parse_context &ctx) -> decltype(ctx.begin()) { return ctx.end(); }
template<typename FormatContext>
auto format(const bspversion_t &v, FormatContext &ctx) -> decltype(ctx.out())
{
if (v.name) {
fmt::format_to(ctx.out(), "{} ", v.name);
}
// Q2-esque BSPs are printed as, ex, IBSP:38
if (v.version.has_value()) {
char ident[5] = {(char)(v.ident & 0xFF), (char)((v.ident >> 8) & 0xFF), (char)((v.ident >> 16) & 0xFF),
(char)((v.ident >> 24) & 0xFF), '\0'};
return fmt::format_to(ctx.out(), "{}:{}", ident, v.version.value());
}
// Q1-esque BSPs are printed as, ex, bsp29
return fmt::format_to(ctx.out(), "{}", v.short_name);
}
};
struct texvecf : qmat<float, 2, 4>
{
using qmat<float, 2, 4>::qmat;
template<typename T2>
constexpr qvec<T2, 2> uvs(const qvec<T2, 3> &pos) const
{
return {(pos[0] * this->at(0, 0) + pos[1] * this->at(0, 1) + pos[2] * this->at(0, 2) + this->at(0, 3)),
(pos[0] * this->at(1, 0) + pos[1] * this->at(1, 1) + pos[2] * this->at(1, 2) + this->at(1, 3))};
}
template<typename T2>
constexpr qvec<T2, 2> uvs(const qvec<T2, 3> &pos, const int32_t &width, const int32_t &height) const
{
return uvs(pos) / qvec<T2, 2>{width, height};
}
// Not blit compatible because qmat is column-major but
// texvecs are row-major
void stream_read(std::istream &stream);
void stream_write(std::ostream &stream) const;
};
// Fmt support
template<>
struct fmt::formatter<texvecf> : fmt::formatter<qmat<float, 2, 4>>
{
};
// type to store a hull index; max 256 hulls, zero is valid.
using hull_index_t = std::optional<uint8_t>;