diff --git a/include/common/bspfile.hh b/include/common/bspfile.hh index 257e2380..bf79eb9f 100644 --- a/include/common/bspfile.hh +++ b/include/common/bspfile.hh @@ -41,63 +41,6 @@ struct lump_t auto stream_data() { return std::tie(fileofs, filelen); } }; -constexpr int32_t MBSPIDENT = -1; - -constexpr int32_t BSPVERSION = 29; -constexpr int32_t BSP2RMQVERSION = (('B' << 24) | ('S' << 16) | ('P' << 8) | '2'); -constexpr int32_t BSP2VERSION = ('B' | ('S' << 8) | ('P' << 16) | ('2' << 24)); -constexpr int32_t BSPHLVERSION = 30; // 24bit lighting, and private palettes in the textures lump. - -enum q1_lump_t -{ - LUMP_ENTITIES, - LUMP_PLANES, - LUMP_TEXTURES, - LUMP_VERTEXES, - LUMP_VISIBILITY, - LUMP_NODES, - LUMP_TEXINFO, - LUMP_FACES, - LUMP_LIGHTING, - LUMP_CLIPNODES, - LUMP_LEAFS, - LUMP_MARKSURFACES, - LUMP_EDGES, - LUMP_SURFEDGES, - LUMP_MODELS, - - BSP_LUMPS -}; - -constexpr int32_t Q2_BSPVERSION = 38; -constexpr int32_t Q2_BSPIDENT = (('P' << 24) + ('S' << 16) + ('B' << 8) + 'I'); -constexpr int32_t Q2_QBISMIDENT = (('P' << 24) + ('S' << 16) + ('B' << 8) + 'Q'); - -enum q2_lump_t -{ - Q2_LUMP_ENTITIES, - Q2_LUMP_PLANES, - Q2_LUMP_VERTEXES, - Q2_LUMP_VISIBILITY, - Q2_LUMP_NODES, - Q2_LUMP_TEXINFO, - Q2_LUMP_FACES, - Q2_LUMP_LIGHTING, - Q2_LUMP_LEAFS, - Q2_LUMP_LEAFFACES, - Q2_LUMP_LEAFBRUSHES, - Q2_LUMP_EDGES, - Q2_LUMP_SURFEDGES, - Q2_LUMP_MODELS, - Q2_LUMP_BRUSHES, - Q2_LUMP_BRUSHSIDES, - Q2_LUMP_POP, - Q2_LUMP_AREAS, - Q2_LUMP_AREAPORTALS, - - Q2_HEADER_LUMPS -}; - // helper functions to quickly numerically cast mins/maxs // and floor/ceil them in the case of float -> integral template @@ -122,22 +65,6 @@ inline qvec aabb_maxs_cast(const qvec &f, const char *overflow_messa numeric_cast(f[2], overflow_message)}; } -constexpr size_t MAX_MAP_HULLS_H2 = 8; - -struct dmodelh2_t -{ - qvec3f mins; - qvec3f maxs; - qvec3f origin; - std::array headnode; /* hexen2 only uses 6 */ - int32_t visleafs; /* not including the solid leaf 0 */ - int32_t firstface; - int32_t numfaces; - - // serialize for streams - auto stream_data() { return std::tie(mins, maxs, origin, headnode, visleafs, firstface, numfaces); } -}; - // shortcut template to trim (& convert) std::arrays // between two lengths template @@ -156,389 +83,6 @@ constexpr ADest array_cast(const ASrc &src, const char *overflow_message = "src" return dest; } -constexpr size_t MAX_MAP_HULLS_Q1 = 4; - -struct dmodelq1_t -{ - qvec3f mins; - qvec3f maxs; - qvec3f origin; - std::array headnode; /* 4 for backward compat, only 3 hulls exist */ - int32_t visleafs; /* not including the solid leaf 0 */ - int32_t firstface; - int32_t numfaces; - - dmodelq1_t() = default; - - // convert from mbsp_t - dmodelq1_t(const dmodelh2_t &model) - : mins(model.mins), maxs(model.maxs), origin(model.origin), - headnode(array_cast(model.headnode, "dmodelh2_t::headnode")), visleafs(model.visleafs), - firstface(model.firstface), numfaces(model.numfaces) - { - } - - // convert to mbsp_t - operator dmodelh2_t() const - { - return {mins, maxs, origin, array_cast(headnode, "dmodelh2_t::headnode"), - visleafs, firstface, numfaces}; - } - - // serialize for streams - auto stream_data() { return std::tie(mins, maxs, origin, headnode, visleafs, firstface, numfaces); } -}; - -struct q2_dmodel_t -{ - qvec3f mins; - qvec3f maxs; - qvec3f origin; // for sounds or lights - int32_t headnode; - int32_t firstface; - int32_t numfaces; // submodels just draw faces - // without walking the bsp tree - - q2_dmodel_t() = default; - - // convert from mbsp_t - q2_dmodel_t(const dmodelh2_t &model) - : mins(model.mins), maxs(model.maxs), origin(model.origin), headnode(model.headnode[0]), - firstface(model.firstface), numfaces(model.numfaces) - { - } - - // convert to mbsp_t - operator dmodelh2_t() const - { - return {mins, maxs, origin, {headnode}, - 0, // visleafs - firstface, numfaces}; - } - - // serialize for streams - auto stream_data() { return std::tie(mins, maxs, origin, headnode, firstface, numfaces); } -}; - -// structured data from BSP -constexpr size_t MIPLEVELS = 4; -struct dmiptex_t -{ - std::array name; - uint32_t width, height; - std::array offsets; /* four mip maps stored */ - - auto stream_data() { return std::tie(name, width, height, offsets); } -}; - -// miptex in memory -struct miptex_t -{ - std::string name; - uint32_t width, height; - std::array, MIPLEVELS> data; - - static inline uint8_t *copy_bytes(const uint8_t *in, size_t size) - { - uint8_t *bytes = new uint8_t[size]; - memcpy(bytes, in, size); - return bytes; - } - - miptex_t() = default; - miptex_t(const miptex_t ©) : name(copy.name), width(copy.width), height(copy.height) - { - for (int32_t i = 0; i < data.size(); i++) { - if (copy.data[i]) { - data[i] = std::unique_ptr(copy_bytes(copy.data[i].get(), (width >> i) * (height >> i))); - } - } - } - - inline miptex_t &operator=(const miptex_t ©) - { - name = copy.name; - width = copy.width; - height = copy.height; - - for (int32_t i = 0; i < data.size(); i++) { - if (copy.data[i]) { - data[i] = std::unique_ptr(copy_bytes(copy.data[i].get(), (width >> i) * (height >> i))); - } - } - - return *this; - } - - virtual ~miptex_t() { } - - virtual size_t stream_size() const { return sizeof(dmiptex_t) + width * height / 64 * 85; } - - virtual void stream_read(std::istream &stream) - { - auto start = stream.tellg(); - - dmiptex_t dtex; - stream >= dtex; - - name = dtex.name.data(); - - width = dtex.width; - height = dtex.height; - - for (size_t g = 0; g < MIPLEVELS; g++) { - if (dtex.offsets[g] <= 0) { - continue; - } - - stream.seekg(static_cast(start) + dtex.offsets[g]); - const size_t num_bytes = (dtex.width >> g) * (dtex.height >> g); - uint8_t *bytes = new uint8_t[num_bytes]; - stream.read(reinterpret_cast(bytes), num_bytes); - data[g] = std::unique_ptr(bytes); - } - } - - virtual void stream_write(std::ostream &stream) const - { - std::array as_array{}; - memcpy(as_array.data(), name.c_str(), name.size()); - - stream <= as_array <= width <= height; - - uint32_t header_end = sizeof(dmiptex_t); - - for (size_t i = 0; i < MIPLEVELS; i++) { - if (data[i] <= 0) { - stream <= (uint32_t)0; - } else { - stream <= header_end; - header_end += (width >> i) * (height >> i); - } - } - - for (size_t i = 0; i < MIPLEVELS; i++) { - if (data[i]) { - stream.write(reinterpret_cast(data[i].get()), (width >> i) * (height >> i)); - } - } - } -}; - -// Half Life miptex, which includes a palette -struct miptexhl_t : miptex_t -{ - std::vector palette; - - miptexhl_t() = default; - - // convert miptex_t to miptexhl_t - miptexhl_t(const miptex_t ©) : miptex_t(copy) { } - - virtual size_t stream_size() const - { - return miptex_t::stream_size() + sizeof(uint16_t) + (palette.size() * sizeof(qvec3b)); - } - - virtual void stream_read(std::istream &stream) - { - miptex_t::stream_read(stream); - - uint16_t num_colors; - stream >= num_colors; - - palette.resize(num_colors); - stream.read(reinterpret_cast(palette.data()), palette.size()); - } - - virtual void stream_write(std::ostream &stream) const - { - miptex_t::stream_write(stream); - - stream <= static_cast(palette.size()); - - stream.write(reinterpret_cast(palette.data()), palette.size()); - } -}; - -// structured miptex container lump -template -struct dmiptexlump_t -{ - std::vector textures; - - dmiptexlump_t() = default; - - // copy from a different lump type - template - dmiptexlump_t(const dmiptexlump_t ©) - { - textures.reserve(copy.textures.size()); - - for (auto &m : copy.textures) { - textures.emplace_back(m); - } - } - - void stream_read(std::istream &stream, const lump_t &lump) - { - int32_t nummiptex; - stream >= nummiptex; - - for (size_t i = 0; i < nummiptex; i++) { - int32_t mipofs; - - stream >= mipofs; - - miptex_t &tex = textures.emplace_back(); - - if (mipofs < 0) - continue; - - auto pos = stream.tellg(); - - stream.seekg(lump.fileofs + mipofs); - - tex.stream_read(stream); - - stream.seekg(pos); - } - } - - void stream_write(std::ostream &stream) const - { - auto p = (size_t)stream.tellp(); - - stream <= static_cast(textures.size()); - - const size_t header_size = sizeof(int32_t) + (sizeof(int32_t) * textures.size()); - - size_t miptex_offset = 0; - - for (auto &texture : textures) { - if (!texture.name[0]) { - stream <= static_cast(-1); - continue; - } - stream <= static_cast(header_size + miptex_offset); - - miptex_offset += texture.stream_size(); - - if ((p + miptex_offset) % 4) { - miptex_offset += 4 - ((p + miptex_offset) % 4); - } - } - - for (auto &texture : textures) { - if (texture.name[0]) { - if (stream.tellp() % 4) { - constexpr const char pad[4]{}; - stream.write(pad, 4 - (stream.tellp() % 4)); - } - texture.stream_write(stream); - } - } - } -}; - -// 0-2 are axial planes -// 3-5 are non-axial planes snapped to the nearest -enum class plane_type_t -{ - PLANE_INVALID = -1, - PLANE_X = 0, - PLANE_Y = 1, - PLANE_Z = 2, - PLANE_ANYX = 3, - PLANE_ANYY = 4, - PLANE_ANYZ = 5, -}; - -struct dplane_t : qplane3f -{ - int32_t type; - - [[nodiscard]] constexpr dplane_t operator-() const { return {qplane3f::operator-(), type}; } - - // serialize for streams - auto stream_data() { return std::tie(normal, dist, type); } - - // optimized case - template - inline T distance_to_fast(const qvec &point) const - { - switch (static_cast(type)) { - case plane_type_t::PLANE_X: return point[0] - dist; - case plane_type_t::PLANE_Y: return point[1] - dist; - case plane_type_t::PLANE_Z: return point[2] - dist; - default: { - return qplane3f::distance_to(point); - } - } - } -}; - -// Q1 contents - -enum q1_contents_t : int32_t -{ - CONTENTS_EMPTY = -1, - CONTENTS_SOLID = -2, - CONTENTS_WATER = -3, - CONTENTS_SLIME = -4, - CONTENTS_LAVA = -5, - CONTENTS_SKY = -6, - - CONTENTS_MIN = CONTENTS_SKY -}; - -// Q2 contents (from qfiles.h) - -// contents flags are seperate bits -// a given brush can contribute multiple content bits -// multiple brushes can be in a single leaf - -// lower bits are stronger, and will eat weaker brushes completely -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_WINDOW = nth_bit(1), // translucent, but not watery - Q2_CONTENTS_AUX = nth_bit(2), - Q2_CONTENTS_LAVA = nth_bit(3), - Q2_CONTENTS_SLIME = nth_bit(4), - Q2_CONTENTS_WATER = nth_bit(5), - Q2_CONTENTS_MIST = nth_bit(6), - Q2_LAST_VISIBLE_CONTENTS = Q2_CONTENTS_MIST, - 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_LIQUID = (Q2_CONTENTS_LAVA | Q2_CONTENTS_SLIME | Q2_CONTENTS_WATER), // mxd - - // remaining contents are non-visible, and don't eat brushes - - Q2_CONTENTS_AREAPORTAL = nth_bit(15), - - Q2_CONTENTS_PLAYERCLIP = nth_bit(16), - Q2_CONTENTS_MONSTERCLIP = nth_bit(17), - - // currents can be added to any other contents, and may be mixed - Q2_CONTENTS_CURRENT_0 = nth_bit(18), - Q2_CONTENTS_CURRENT_90 = nth_bit(19), - Q2_CONTENTS_CURRENT_180 = nth_bit(20), - Q2_CONTENTS_CURRENT_270 = nth_bit(21), - Q2_CONTENTS_CURRENT_UP = nth_bit(22), - Q2_CONTENTS_CURRENT_DOWN = nth_bit(23), - - Q2_CONTENTS_ORIGIN = nth_bit(24), // removed before bsping an entity - - Q2_CONTENTS_MONSTER = nth_bit(25), // should never be on a brush, only in game - Q2_CONTENTS_DEADMONSTER = nth_bit(26), - Q2_CONTENTS_DETAIL = nth_bit(27), // brushes to be added after vis leafs - Q2_CONTENTS_TRANSLUCENT = nth_bit(28), // auto set if any surface has trans - Q2_CONTENTS_LADDER = nth_bit(29) -}; - struct gamedef_t; struct contentflags_t @@ -614,202 +158,6 @@ struct contentflags_t std::string to_string(const gamedef_t *game) const; }; -struct bsp2_dnode_t -{ - int32_t planenum; - std::array children; /* negative numbers are -(leafs+1), not nodes */ - qvec3f mins; /* for sphere culling */ - qvec3f maxs; - uint32_t firstface; - uint32_t numfaces; /* counting both sides */ - - // serialize for streams - auto stream_data() { return std::tie(planenum, children, mins, maxs, firstface, numfaces); } -}; - -struct bsp29_dnode_t -{ - int32_t planenum; - std::array - children; /* negative numbers are -(leafs+1), not nodes. children[0] is front, children[1] is back */ - qvec3s mins; /* for sphere culling */ - qvec3s maxs; - uint16_t firstface; - uint16_t numfaces; /* counting both sides */ - - bsp29_dnode_t() = default; - - // convert from mbsp_t - bsp29_dnode_t(const bsp2_dnode_t &model) - : planenum(model.planenum), children(array_cast(model.children, "dnode_t::children")), - mins(aabb_mins_cast(model.mins, "dnode_t::mins")), - maxs(aabb_maxs_cast(model.maxs, "dnode_t::maxs")), - firstface(numeric_cast(model.firstface, "dnode_t::firstface")), - numfaces(numeric_cast(model.numfaces, "dnode_t::numfaces")) - { - } - - // convert to mbsp_t - operator bsp2_dnode_t() const - { - return {planenum, array_cast(children, "dnode_t::children"), - aabb_mins_cast(mins, "dnode_t::mins"), aabb_mins_cast(maxs, "dnode_t::maxs"), firstface, - numfaces}; - } - - // serialize for streams - auto stream_data() { return std::tie(planenum, children, mins, maxs, firstface, numfaces); } -}; - -struct bsp2rmq_dnode_t -{ - int32_t planenum; - std::array children; /* negative numbers are -(leafs+1), not nodes */ - qvec3s mins; /* for sphere culling */ - qvec3s maxs; - uint32_t firstface; - uint32_t numfaces; /* counting both sides */ - - bsp2rmq_dnode_t() = default; - - // convert from mbsp_t - bsp2rmq_dnode_t(const bsp2_dnode_t &model) - : planenum(model.planenum), children(model.children), - mins(aabb_mins_cast(model.mins, "dnode_t::mins")), - maxs(aabb_maxs_cast(model.maxs, "dnode_t::maxs")), firstface(model.firstface), - numfaces(model.numfaces) - { - } - - // convert to mbsp_t - operator bsp2_dnode_t() const - { - return {planenum, children, aabb_mins_cast(mins, "dnode_t::mins"), - aabb_mins_cast(maxs, "dnode_t::maxs"), firstface, numfaces}; - } - - // serialize for streams - auto stream_data() { return std::tie(planenum, children, mins, maxs, firstface, numfaces); } -}; - -struct q2_dnode_t -{ - int32_t planenum; - std::array children; // negative numbers are -(leafs+1), not nodes - qvec3s mins; // for frustom culling - qvec3s maxs; - uint16_t firstface; - uint16_t numfaces; // counting both sides - - q2_dnode_t() = default; - - // convert from mbsp_t - q2_dnode_t(const bsp2_dnode_t &model) - : planenum(model.planenum), children(model.children), - mins(aabb_mins_cast(model.mins, "dnode_t::mins")), - maxs(aabb_maxs_cast(model.maxs, "dnode_t::maxs")), - firstface(numeric_cast(model.firstface, "dnode_t::firstface")), - numfaces(numeric_cast(model.numfaces, "dnode_t::numfaces")) - { - } - - // convert to mbsp_t - operator bsp2_dnode_t() const - { - return {planenum, children, aabb_mins_cast(mins, "dnode_t::mins"), - aabb_mins_cast(maxs, "dnode_t::maxs"), firstface, numfaces}; - } - - // serialize for streams - auto stream_data() { return std::tie(planenum, children, mins, maxs, firstface, numfaces); } -}; - -using q2_dnode_qbism_t = bsp2_dnode_t; - -/* - * Note that children are interpreted as unsigned values now, so that we can - * handle > 32k clipnodes. Values > 0xFFF0 can be assumed to be CONTENTS - * values and can be read as the signed value to be compatible with the above - * (i.e. simply subtract 65536). - */ -struct bsp2_dclipnode_t -{ - int32_t planenum; - std::array children; /* negative numbers are contents */ - - // serialize for streams - auto stream_data() { return std::tie(planenum, children); } -}; - -/* -* Clipnodes need to be stored as a 16-bit offset. Originally, this was a -* signed value and only the positive values up to 32767 were available. Since -* the negative range was unused apart from a few values reserved for flags, -* this has been extended to allow up to 65520 (0xfff0) clipnodes (with a -* suitably modified engine). -*/ -struct bsp29_dclipnode_t -{ - int32_t planenum; - std::array children; /* negative numbers are contents */ - - bsp29_dclipnode_t() = default; - - // convert from mbsp_t - bsp29_dclipnode_t(const bsp2_dclipnode_t &model) - : planenum(model.planenum), children({downcast(model.children[0]), downcast(model.children[1])}) - { - } - - // convert to mbsp_t - operator bsp2_dclipnode_t() const { return {planenum, {upcast(children[0]), upcast(children[1])}}; } - - // serialize for streams - auto stream_data() { return std::tie(planenum, children); } - - /* Slightly tricky since we support > 32k clipnodes */ -private: - static constexpr int16_t downcast(const int32_t &v) - { - if (v < -15 || v > 0xFFF0) - throw std::overflow_error("dclipmode_t::children"); - - return static_cast(v < 0 ? v + 0x10000 : v); - } - - static constexpr int32_t upcast(const int16_t &v) - { - int32_t child = (uint16_t)v; - return child > 0xfff0 ? child - 0x10000 : child; - } -}; - -// Q1 Texture flags. -enum q1_surf_flags_t : int32_t -{ - TEX_SPECIAL = nth_bit(0) /* sky or slime, no lightmap or 256 subdivision */ -}; - -// Q2 Texture flags. -enum q2_surf_flags_t : int32_t -{ - Q2_SURF_LIGHT = nth_bit(0), // value will hold the light strength - - Q2_SURF_SLICK = nth_bit(1), // effects game physics - - Q2_SURF_SKY = nth_bit(2), // don't draw, but add to skybox - Q2_SURF_WARP = nth_bit(3), // turbulent water warp - Q2_SURF_TRANS33 = nth_bit(4), - Q2_SURF_TRANS66 = nth_bit(5), - Q2_SURF_FLOWING = nth_bit(6), // scroll towards angle - Q2_SURF_NODRAW = nth_bit(7), // don't bother referencing the texture - - Q2_SURF_HINT = nth_bit(8), // make a primary bsp splitter - Q2_SURF_SKIP = nth_bit(9), // ONLY FOR HINT! "nodraw" = Q1 "skip" - - Q2_SURF_TRANSLUCENT = (Q2_SURF_TRANS33 | Q2_SURF_TRANS66), // mxd -}; - struct surfflags_t { // native flags value; what's written to the BSP basically @@ -876,812 +224,6 @@ public: bool is_valid(const gamedef_t *game) const; }; -template -struct texvec : qmat -{ - using qmat::qmat; - - template - constexpr qvec uvs(const qvec &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 - constexpr qvec uvs(const qvec &pos, const int32_t &width, const int32_t &height) const - { - return uvs(pos) / qvec{width, height}; - } - - // Not blit compatible because qmat is column-major but - // texvecs are row-major - - void stream_read(std::istream &stream) - { - for (size_t i = 0; i < 2; i++) - for (size_t x = 0; x < 4; x++) { - stream >= this->at(i, x); - } - } - - void stream_write(std::ostream &stream) const - { - for (size_t i = 0; i < 2; i++) - for (size_t x = 0; x < 4; x++) { - stream <= this->at(i, x); - } - } -}; - -// Fmt support -template -struct fmt::formatter> : formatter> -{ -}; - -using texvecf = texvec; - -struct mtexinfo_t -{ - texvecf vecs; // [s/t][xyz offset] - surfflags_t flags; // native miptex flags + extended flags - - // q1 only - int32_t miptex; - - // q2 only - int32_t value; // light emission, etc - std::array texture; // texture name (textures/*.wal) - int32_t nexttexinfo = -1; // for animations, -1 = end of chain -}; - -struct texinfo_t -{ - texvecf vecs; /* [s/t][xyz offset] */ - int32_t miptex; - int32_t flags; - - texinfo_t() = default; - - // convert from mbsp_t - texinfo_t(const mtexinfo_t &model) : vecs(model.vecs), miptex(model.miptex), flags(model.flags.native) { } - - // convert to mbsp_t - operator mtexinfo_t() const { return {vecs, {flags}, miptex}; } - - // serialize for streams - auto stream_data() { return std::tie(vecs, miptex, flags); } -}; - -struct q2_texinfo_t -{ - texvecf vecs; // [s/t][xyz offset] - int32_t flags; // miptex flags + overrides - int32_t value; // light emission, etc - std::array texture; // texture name (textures/*.wal) - int32_t nexttexinfo; // for animations, -1 = end of chain - - q2_texinfo_t() = default; - - // convert from mbsp_t - q2_texinfo_t(const mtexinfo_t &model) - : vecs(model.vecs), flags(model.flags.native), value(model.value), texture(model.texture), - nexttexinfo(model.nexttexinfo) - { - } - - // convert to mbsp_t - operator mtexinfo_t() const { return {vecs, {flags}, -1, value, texture, nexttexinfo}; } - - // serialize for streams - auto stream_data() { return std::tie(vecs, flags, value, texture, nexttexinfo); } -}; - -/* - * Note that edge 0 is never used, because negative edge nums are used for - * counterclockwise use of the edge in a face - */ -using bsp29_dedge_t = std::array; /* vertex numbers */ -using bsp2_dedge_t = std::array; /* vertex numbers */ - -constexpr size_t MAXLIGHTMAPS = 4; - -struct mface_t -{ - int64_t planenum; - int32_t side; // if true, the face is on the back side of the plane - int32_t firstedge; /* we must support > 64k edges */ - int32_t numedges; - int32_t texinfo; - - /* lighting info */ - std::array styles; - int32_t lightofs; /* start of [numstyles*surfsize] samples */ - - // serialize for streams - auto stream_data() { return std::tie(planenum, side, firstedge, numedges, texinfo, styles, lightofs); } -}; - -struct bsp29_dface_t -{ - int16_t planenum; - int16_t side; - int32_t firstedge; /* we must support > 64k edges */ - int16_t numedges; - int16_t texinfo; - - /* lighting info */ - std::array styles; - int32_t lightofs; /* start of [numstyles*surfsize] samples */ - - bsp29_dface_t() = default; - - // convert from mbsp_t - bsp29_dface_t(const mface_t &model) - : planenum(numeric_cast(model.planenum, "dface_t::planenum")), - side(numeric_cast(model.side, "dface_t::side")), firstedge(model.firstedge), - numedges(numeric_cast(model.numedges, "dface_t::numedges")), - texinfo(numeric_cast(model.texinfo, "dface_t::texinfo")), styles(model.styles), - lightofs(model.lightofs) - { - } - - // convert to mbsp_t - operator mface_t() const { return {planenum, side, firstedge, numedges, texinfo, styles, lightofs}; } - - // serialize for streams - auto stream_data() { return std::tie(planenum, side, firstedge, numedges, texinfo, styles, lightofs); } -}; - -struct bsp2_dface_t -{ - int32_t planenum; - int32_t side; // if true, the face is on the back side of the plane - int32_t firstedge; /* we must support > 64k edges */ - int32_t numedges; - int32_t texinfo; - - /* lighting info */ - std::array styles; - int32_t lightofs; /* start of [numstyles*surfsize] samples */ - - bsp2_dface_t() = default; - - // convert from mbsp_t - bsp2_dface_t(const mface_t &model) - : planenum(numeric_cast(model.planenum, "dface_t::planenum")), side(model.side), - firstedge(model.firstedge), numedges(model.numedges), texinfo(model.texinfo), styles(model.styles), - lightofs(model.lightofs) - { - } - - // convert to mbsp_t - operator mface_t() const { return {planenum, side, firstedge, numedges, texinfo, styles, lightofs}; } - - // serialize for streams - auto stream_data() { return std::tie(planenum, side, firstedge, numedges, texinfo, styles, lightofs); } -}; - -struct q2_dface_t -{ - uint16_t planenum; // NOTE: only difference from bsp29_dface_t - int16_t side; - int32_t firstedge; // we must support > 64k edges - int16_t numedges; - int16_t texinfo; - - // lighting info - std::array styles; - int32_t lightofs; // start of [numstyles*surfsize] samples - - q2_dface_t() = default; - - // convert from mbsp_t - q2_dface_t(const mface_t &model) - : planenum(numeric_cast(model.planenum, "dface_t::planenum")), - side(numeric_cast(model.side, "dface_t::side")), firstedge(model.firstedge), - numedges(numeric_cast(model.numedges, "dface_t::numedges")), - texinfo(numeric_cast(model.texinfo, "dface_t::texinfo")), styles(model.styles), - lightofs(model.lightofs) - { - } - - // convert to mbsp_t - operator mface_t() const { return {planenum, side, firstedge, numedges, texinfo, styles, lightofs}; } - - // serialize for streams - auto stream_data() { return std::tie(planenum, side, firstedge, numedges, texinfo, styles, lightofs); } -}; - -struct q2_dface_qbism_t -{ - uint32_t planenum; // NOTE: only difference from bsp2_dface_t - int32_t side; - int32_t firstedge; // we must support > 64k edges - int32_t numedges; - int32_t texinfo; - - // lighting info - std::array styles; - int32_t lightofs; // start of [numstyles*surfsize] samples - - q2_dface_qbism_t() = default; - - // convert from mbsp_t - q2_dface_qbism_t(const mface_t &model) - : planenum(numeric_cast(model.planenum, "dface_t::planenum")), side(model.side), - firstedge(model.firstedge), numedges(model.numedges), texinfo(model.texinfo), styles(model.styles), - lightofs(model.lightofs) - { - } - - // convert to mbsp_t - operator mface_t() const { return {planenum, side, firstedge, numedges, texinfo, styles, lightofs}; } - - // serialize for streams - auto stream_data() { return std::tie(planenum, side, firstedge, numedges, texinfo, styles, lightofs); } -}; - -/* - * leaf 0 is the generic CONTENTS_SOLID leaf, used for all solid areas (except Q2) - * all other leafs need visibility info - */ -/* Ambient Sounds */ -enum ambient_type_t : uint8_t -{ - AMBIENT_WATER, - AMBIENT_SKY, - AMBIENT_SLIME, - AMBIENT_LAVA, - - NUM_AMBIENTS = 4 -}; - -struct mleaf_t -{ - // bsp2_dleaf_t - int32_t contents; - int32_t visofs; /* -1 = no visibility info */ - qvec3f mins; /* for frustum culling */ - qvec3f maxs; - uint32_t firstmarksurface; - uint32_t nummarksurfaces; - std::array ambient_level; - - // q2 extras - int32_t cluster; - int32_t area; - uint32_t firstleafbrush; - uint32_t numleafbrushes; -}; - -struct bsp29_dleaf_t -{ - int32_t contents; - int32_t visofs; /* -1 = no visibility info */ - qvec3s mins; /* for frustum culling */ - qvec3s maxs; - uint16_t firstmarksurface; - uint16_t nummarksurfaces; - std::array ambient_level; - - bsp29_dleaf_t() = default; - - // convert from mbsp_t - bsp29_dleaf_t(const mleaf_t &model) - : contents(model.contents), visofs(model.visofs), mins(aabb_mins_cast(model.mins, "dleaf_t::mins")), - maxs(aabb_maxs_cast(model.maxs, "dleaf_t::maxs")), - firstmarksurface(numeric_cast(model.firstmarksurface, "dleaf_t::firstmarksurface")), - nummarksurfaces(numeric_cast(model.nummarksurfaces, "dleaf_t::nummarksurfaces")), - ambient_level(model.ambient_level) - { - } - - // convert to mbsp_t - operator mleaf_t() const - { - return {contents, visofs, aabb_mins_cast(mins, "dleaf_t::mins"), - aabb_mins_cast(maxs, "dleaf_t::maxs"), firstmarksurface, nummarksurfaces, ambient_level}; - } - - // serialize for streams - auto stream_data() - { - return std::tie(contents, visofs, mins, maxs, firstmarksurface, nummarksurfaces, ambient_level); - } -}; - -struct bsp2rmq_dleaf_t -{ - int32_t contents; - int32_t visofs; /* -1 = no visibility info */ - qvec3s mins; /* for frustum culling */ - qvec3s maxs; - uint32_t firstmarksurface; - uint32_t nummarksurfaces; - std::array ambient_level; - - bsp2rmq_dleaf_t() = default; - - // convert from mbsp_t - bsp2rmq_dleaf_t(const mleaf_t &model) - : contents(model.contents), visofs(model.visofs), mins(aabb_mins_cast(model.mins, "dleaf_t::mins")), - maxs(aabb_maxs_cast(model.maxs, "dleaf_t::maxs")), firstmarksurface(model.firstmarksurface), - nummarksurfaces(model.nummarksurfaces), ambient_level(model.ambient_level) - { - } - - // convert to mbsp_t - operator mleaf_t() const - { - return {contents, visofs, aabb_mins_cast(mins, "dleaf_t::mins"), - aabb_mins_cast(maxs, "dleaf_t::maxs"), firstmarksurface, nummarksurfaces, ambient_level}; - } - - // serialize for streams - auto stream_data() - { - return std::tie(contents, visofs, mins, maxs, firstmarksurface, nummarksurfaces, ambient_level); - } -}; - -struct bsp2_dleaf_t -{ - int32_t contents; - int32_t visofs; /* -1 = no visibility info */ - qvec3f mins; /* for frustum culling */ - qvec3f maxs; - uint32_t firstmarksurface; - uint32_t nummarksurfaces; - std::array ambient_level; - - bsp2_dleaf_t() = default; - - // convert from mbsp_t - bsp2_dleaf_t(const mleaf_t &model) - : contents(model.contents), visofs(model.visofs), mins(model.mins), maxs(model.maxs), - firstmarksurface(model.firstmarksurface), nummarksurfaces(model.nummarksurfaces), - ambient_level(model.ambient_level) - { - } - - // convert to mbsp_t - operator mleaf_t() const - { - return {contents, visofs, mins, maxs, firstmarksurface, nummarksurfaces, ambient_level}; - } - - // serialize for streams - auto stream_data() - { - return std::tie(contents, visofs, mins, maxs, firstmarksurface, nummarksurfaces, ambient_level); - } -}; - -struct q2_dleaf_t -{ - int32_t contents; // OR of all brushes (not needed?) - - int16_t cluster; - int16_t area; - - qvec3s mins; // for frustum culling - qvec3s maxs; - - uint16_t firstleafface; - uint16_t numleaffaces; - - uint16_t firstleafbrush; - uint16_t numleafbrushes; - - q2_dleaf_t() = default; - - // convert from mbsp_t - q2_dleaf_t(const mleaf_t &model) - : contents(model.contents), cluster(numeric_cast(model.cluster, "dleaf_t::cluster")), - area(numeric_cast(model.area, "dleaf_t::area")), - mins(aabb_mins_cast(model.mins, "dleaf_t::mins")), - maxs(aabb_mins_cast(model.maxs, "dleaf_t::maxs")), - firstleafface(numeric_cast(model.firstmarksurface, "dleaf_t::firstmarksurface")), - numleaffaces(numeric_cast(model.nummarksurfaces, "dleaf_t::nummarksurfaces")), - firstleafbrush(numeric_cast(model.firstleafbrush, "dleaf_t::firstleafbrush")), - numleafbrushes(numeric_cast(model.numleafbrushes, "dleaf_t::numleafbrushes")) - { - } - - // convert to mbsp_t - operator mleaf_t() const - { - return {contents, -1, aabb_mins_cast(mins, "dleaf_t::mins"), - aabb_mins_cast(maxs, "dleaf_t::maxs"), firstleafface, numleaffaces, {}, cluster, area, - firstleafbrush, numleafbrushes}; - } - - // serialize for streams - auto stream_data() - { - return std::tie( - contents, cluster, area, mins, maxs, firstleafface, numleaffaces, firstleafbrush, numleafbrushes); - } -}; - -struct q2_dleaf_qbism_t -{ - int32_t contents; // OR of all brushes (not needed?) - - int32_t cluster; - int32_t area; - - qvec3f mins; // for frustum culling - qvec3f maxs; - - uint32_t firstleafface; - uint32_t numleaffaces; - - uint32_t firstleafbrush; - uint32_t numleafbrushes; - - q2_dleaf_qbism_t() = default; - - // convert from mbsp_t - q2_dleaf_qbism_t(const mleaf_t &model) - : contents(model.contents), cluster(model.cluster), area(model.area), mins(model.mins), maxs(model.maxs), - firstleafface(model.firstmarksurface), numleaffaces(model.nummarksurfaces), - firstleafbrush(model.firstleafbrush), numleafbrushes(model.numleafbrushes) - { - } - - // convert to mbsp_t - operator mleaf_t() const - { - return { - contents, -1, mins, maxs, firstleafface, numleaffaces, {}, cluster, area, firstleafbrush, numleafbrushes}; - } - - // serialize for streams - auto stream_data() - { - return std::tie( - contents, cluster, area, mins, maxs, firstleafface, numleaffaces, firstleafbrush, numleafbrushes); - } -}; - -struct q2_dbrushside_qbism_t -{ - uint32_t planenum; // facing out of the leaf - int32_t texinfo; - - // serialize for streams - auto stream_data() { return std::tie(planenum, texinfo); } -}; - -struct q2_dbrushside_t -{ - uint16_t planenum; // facing out of the leaf - int16_t texinfo; - - q2_dbrushside_t() = default; - - // convert from mbsp_t - q2_dbrushside_t(const q2_dbrushside_qbism_t &model) - : planenum(numeric_cast(model.planenum, "dbrushside_t::planenum")), - texinfo(numeric_cast(model.texinfo, "dbrushside_t::texinfo")) - { - } - - // convert to mbsp_t - operator q2_dbrushside_qbism_t() const { return {planenum, texinfo}; } - - // serialize for streams - auto stream_data() { return std::tie(planenum, texinfo); } -}; - -struct dbrush_t -{ - int32_t firstside; - int32_t numsides; - int32_t contents; - - // serialize for streams - auto stream_data() { return std::tie(firstside, numsides, contents); } -}; - -enum vistype_t -{ - VIS_PVS, - VIS_PHS -}; - -// the visibility lump consists of a header with a count, then -// byte offsets for the PVS and PHS of each cluster, then the raw -// compressed bit vectors. -struct mvis_t -{ - std::vector> bit_offsets; - std::vector bits; - - inline size_t header_offset() const { return sizeof(int32_t) + (sizeof(int32_t) * bit_offsets.size() * 2); } - - // set a bit offset of the specified cluster/vistype *relative to the start of the bits array* - // (after the header) - inline void set_bit_offset(vistype_t type, size_t cluster, size_t offset) - { - bit_offsets[cluster][type] = offset + header_offset(); - } - - // fetch the bit offset of the specified cluster/vistype - // relative to the start of the bits array - inline int32_t get_bit_offset(vistype_t type, size_t cluster) - { - return bit_offsets[cluster][type] - header_offset(); - } - - void resize(size_t numclusters) { bit_offsets.resize(numclusters); } - - void stream_read(std::istream &stream, const lump_t &lump) - { - int32_t numclusters; - - stream >= numclusters; - - resize(numclusters); - - // read cluster -> offset tables - for (auto &bit_offset : bit_offsets) - stream >= bit_offset; - - // pull in final bit set - auto remaining = lump.filelen - (static_cast(stream.tellg()) - lump.fileofs); - bits.resize(remaining); - stream.read(reinterpret_cast(bits.data()), remaining); - } - - void stream_write(std::ostream &stream) const - { - // no vis data - if (!bit_offsets.size()) { - return; - } - - stream <= static_cast(bit_offsets.size()); - - // write cluster -> offset tables - for (auto &bit_offset : bit_offsets) - stream <= bit_offset; - - // write bitset - stream.write(reinterpret_cast(bits.data()), bits.size()); - } -}; - -// each area has a list of portals that lead into other areas -// when portals are closed, other areas may not be visible or -// hearable even if the vis info says that it should be -struct dareaportal_t -{ - int32_t portalnum; - int32_t otherarea; - - // serialize for streams - auto stream_data() { return std::tie(portalnum, otherarea); } - auto tuple() const { return std::tie(portalnum, otherarea); } - - // comparison operator for tests - bool operator==(const dareaportal_t &other) const { return tuple() == other.tuple(); } -}; - -struct darea_t -{ - int32_t numareaportals; - int32_t firstareaportal; - - // serialize for streams - auto stream_data() { return std::tie(numareaportals, firstareaportal); } - auto tuple() const { return std::tie(numareaportals, firstareaportal); } - - // comparison operator for tests - bool operator==(const darea_t &other) const { return tuple() == other.tuple(); } -}; - -// Q1-esque maps can use one of these two. -using dmodelq1_vector = std::vector; -using dmodelh2_vector = std::vector; - -// Q1-esque maps can use one of these two. -using miptexq1_lump = dmiptexlump_t; -using miptexhl_lump = dmiptexlump_t; - -// type tag used for type inference -struct q1bsp_tag_t -{ -}; - -struct bsp29_t : q1bsp_tag_t -{ - std::variant dmodels; - std::vector dvisdata; - std::vector dlightdata; - std::variant dtex; - std::string dentdata; - std::vector dleafs; - std::vector dplanes; - std::vector dvertexes; - std::vector dnodes; - std::vector texinfo; - std::vector dfaces; - std::vector dclipnodes; - std::vector dedges; - std::vector dmarksurfaces; - std::vector dsurfedges; -}; - -struct bsp2rmq_t : q1bsp_tag_t -{ - std::variant dmodels; - std::vector dvisdata; - std::vector dlightdata; - std::variant dtex; - std::string dentdata; - std::vector dleafs; - std::vector dplanes; - std::vector dvertexes; - std::vector dnodes; - std::vector texinfo; - std::vector dfaces; - std::vector dclipnodes; - std::vector dedges; - std::vector dmarksurfaces; - std::vector dsurfedges; -}; - -struct bsp2_t : q1bsp_tag_t -{ - std::variant dmodels; - std::vector dvisdata; - std::vector dlightdata; - std::variant dtex; - std::string dentdata; - std::vector dleafs; - std::vector dplanes; - std::vector dvertexes; - std::vector dnodes; - std::vector texinfo; - std::vector dfaces; - std::vector dclipnodes; - std::vector dedges; - std::vector dmarksurfaces; - std::vector dsurfedges; -}; - -// type tag used for type inference -struct q2bsp_tag_t -{ -}; - -struct q2bsp_t : q2bsp_tag_t -{ - std::vector dmodels; - - mvis_t dvis; - - std::vector dlightdata; - std::string dentdata; - std::vector dleafs; - std::vector dplanes; - std::vector dvertexes; - std::vector dnodes; - std::vector texinfo; - std::vector dfaces; - std::vector dedges; - std::vector dleaffaces; - std::vector dleafbrushes; - std::vector dsurfedges; - std::vector dareas; - std::vector dareaportals; - std::vector dbrushes; - std::vector dbrushsides; -}; - -struct q2bsp_qbism_t : q2bsp_tag_t -{ - std::vector dmodels; - mvis_t dvis; - std::vector dlightdata; - std::string dentdata; - std::vector dleafs; - std::vector dplanes; - std::vector dvertexes; - std::vector dnodes; - std::vector texinfo; - std::vector dfaces; - std::vector dedges; - std::vector dleaffaces; - std::vector dleafbrushes; - std::vector dsurfedges; - std::vector dareas; - std::vector dareaportals; - std::vector dbrushes; - std::vector dbrushsides; -}; - -struct bspversion_t; - -struct mbsp_t -{ - const bspversion_t *loadversion; - - std::vector dmodels; - mvis_t dvis; - std::vector dlightdata; - dmiptexlump_t dtex; - std::string dentdata; - std::vector dleafs; - std::vector dplanes; - std::vector dvertexes; - std::vector dnodes; - std::vector texinfo; - std::vector dfaces; - std::vector dclipnodes; - std::vector dedges; - std::vector dleaffaces; - std::vector dleafbrushes; - std::vector dsurfedges; - std::vector dareas; - std::vector dareaportals; - std::vector dbrushes; - std::vector dbrushsides; -}; // "generic" bsp - superset of all other supported types - -struct dheader_t -{ - int32_t ident; - std::array lumps; - - auto stream_data() { return std::tie(ident, lumps); } -}; - -struct q2_dheader_t -{ - int32_t ident; - int32_t version; - std::array lumps; - - auto stream_data() { return std::tie(ident, version, lumps); } -}; - -#include "bspxfile.hh" - -struct bspdata_t -{ - const bspversion_t *version, *loadversion; - - // Stay in monostate until a BSP type is requested. - std::variant bsp; - - // This can be used with any BSP format. - struct - { - std::unordered_map entries; - - // convenience function to transfer a generic pointer into - // the entries list - inline void transfer(const char *xname, uint8_t *&xdata, size_t xsize) - { - entries.insert_or_assign(xname, bspxentry_t{xdata, xsize}); - xdata = nullptr; - } - - // copies the data over to the BSP - void copy(const char *xname, const uint8_t *xdata, size_t xsize) - { - uint8_t *copy = new uint8_t[xsize]; - memcpy(copy, xdata, xsize); - transfer(xname, copy, xsize); - } - } bspx; -}; - // native game target ID enum gameid_t { @@ -1699,7 +241,7 @@ enum gameid_t struct gamedef_t { // ID, used for quick comparisons - gameid_t id; + gameid_t id = GAME_UNKNOWN; // whether the game uses an RGB lightmap or not bool has_rgb_lightmap = false; @@ -1709,7 +251,7 @@ struct gamedef_t // 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; + const std::string default_base_dir = {}; // max values of entity key & value pairs, only used for // printing warnings. @@ -1814,16 +356,86 @@ struct fmt::formatter } }; -extern const bspversion_t bspver_generic; -extern const bspversion_t bspver_q1; -extern const bspversion_t bspver_h2; -extern const bspversion_t bspver_h2bsp2; -extern const bspversion_t bspver_h2bsp2rmq; -extern const bspversion_t bspver_bsp2; -extern const bspversion_t bspver_bsp2rmq; -extern const bspversion_t bspver_hl; -extern const bspversion_t bspver_q2; -extern const bspversion_t bspver_qbism; +template +struct texvec : qmat +{ + using qmat::qmat; + + template + constexpr qvec uvs(const qvec &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 + constexpr qvec uvs(const qvec &pos, const int32_t &width, const int32_t &height) const + { + return uvs(pos) / qvec{width, height}; + } + + // Not blit compatible because qmat is column-major but + // texvecs are row-major + + void stream_read(std::istream &stream) + { + for (size_t i = 0; i < 2; i++) + for (size_t x = 0; x < 4; x++) { + stream >= this->at(i, x); + } + } + + void stream_write(std::ostream &stream) const + { + for (size_t i = 0; i < 2; i++) + for (size_t x = 0; x < 4; x++) { + stream <= this->at(i, x); + } + } +}; + +// Fmt support +template +struct fmt::formatter> : formatter> +{ +}; + +using texvecf = texvec; + +#include "bspfile_generic.hh" +#include "bspfile_q1.hh" +#include "bspfile_q2.hh" +#include "bspxfile.hh" + +struct bspdata_t +{ + const bspversion_t *version, *loadversion; + + // Stay in monostate until a BSP type is requested. + std::variant bsp; + + // This can be used with any BSP format. + struct + { + std::unordered_map entries; + + // convenience function to transfer a generic pointer into + // the entries list + inline void transfer(const char *xname, uint8_t *&xdata, size_t xsize) + { + entries.insert_or_assign(xname, bspxentry_t{xdata, xsize}); + xdata = nullptr; + } + + // copies the data over to the BSP + void copy(const char *xname, const uint8_t *xdata, size_t xsize) + { + uint8_t *copy = new uint8_t[xsize]; + memcpy(copy, xdata, xsize); + transfer(xname, copy, xsize); + } + } bspx; +}; /* table of supported versions */ constexpr const bspversion_t *const bspversions[] = {&bspver_generic, &bspver_q1, &bspver_h2, &bspver_h2bsp2, diff --git a/include/common/bspfile_generic.hh b/include/common/bspfile_generic.hh new file mode 100644 index 00000000..ff5a9a46 --- /dev/null +++ b/include/common/bspfile_generic.hh @@ -0,0 +1,546 @@ +/* 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 +#include +#include +#include +#include +#include "qvec.hh" + +constexpr int32_t MBSPIDENT = -1; + +constexpr size_t MAX_MAP_HULLS_H2 = 8; + +struct dmodelh2_t +{ + qvec3f mins; + qvec3f maxs; + qvec3f origin; + std::array headnode; /* hexen2 only uses 6 */ + int32_t visleafs; /* not including the solid leaf 0 */ + int32_t firstface; + int32_t numfaces; + + // serialize for streams + auto stream_data() { return std::tie(mins, maxs, origin, headnode, visleafs, firstface, numfaces); } +}; + +enum vistype_t +{ + VIS_PVS, + VIS_PHS +}; + +// the visibility lump consists of a header with a count, then +// byte offsets for the PVS and PHS of each cluster, then the raw +// compressed bit vectors. +struct mvis_t +{ + std::vector> bit_offsets; + std::vector bits; + + inline size_t header_offset() const { return sizeof(int32_t) + (sizeof(int32_t) * bit_offsets.size() * 2); } + + // set a bit offset of the specified cluster/vistype *relative to the start of the bits array* + // (after the header) + inline void set_bit_offset(vistype_t type, size_t cluster, size_t offset) + { + bit_offsets[cluster][type] = offset + header_offset(); + } + + // fetch the bit offset of the specified cluster/vistype + // relative to the start of the bits array + inline int32_t get_bit_offset(vistype_t type, size_t cluster) + { + return bit_offsets[cluster][type] - header_offset(); + } + + void resize(size_t numclusters) { bit_offsets.resize(numclusters); } + + void stream_read(std::istream &stream, const lump_t &lump) + { + int32_t numclusters; + + stream >= numclusters; + + resize(numclusters); + + // read cluster -> offset tables + for (auto &bit_offset : bit_offsets) + stream >= bit_offset; + + // pull in final bit set + auto remaining = lump.filelen - (static_cast(stream.tellg()) - lump.fileofs); + bits.resize(remaining); + stream.read(reinterpret_cast(bits.data()), remaining); + } + + void stream_write(std::ostream &stream) const + { + // no vis data + if (!bit_offsets.size()) { + return; + } + + stream <= static_cast(bit_offsets.size()); + + // write cluster -> offset tables + for (auto &bit_offset : bit_offsets) + stream <= bit_offset; + + // write bitset + stream.write(reinterpret_cast(bits.data()), bits.size()); + } +}; + +constexpr size_t MIPLEVELS = 4; + +struct dmiptex_t +{ + std::array name; + uint32_t width, height; + std::array offsets; /* four mip maps stored */ + + auto stream_data() { return std::tie(name, width, height, offsets); } +}; + +// miptex in memory +struct miptex_t +{ + std::string name; + uint32_t width, height; + std::array, MIPLEVELS> data; + + static inline uint8_t *copy_bytes(const uint8_t *in, size_t size) + { + uint8_t *bytes = new uint8_t[size]; + memcpy(bytes, in, size); + return bytes; + } + + miptex_t() = default; + miptex_t(const miptex_t ©) : name(copy.name), width(copy.width), height(copy.height) + { + for (int32_t i = 0; i < data.size(); i++) { + if (copy.data[i]) { + data[i] = std::unique_ptr(copy_bytes(copy.data[i].get(), (width >> i) * (height >> i))); + } + } + } + + inline miptex_t &operator=(const miptex_t ©) + { + name = copy.name; + width = copy.width; + height = copy.height; + + for (int32_t i = 0; i < data.size(); i++) { + if (copy.data[i]) { + data[i] = std::unique_ptr(copy_bytes(copy.data[i].get(), (width >> i) * (height >> i))); + } + } + + return *this; + } + + virtual ~miptex_t() { } + + virtual size_t stream_size() const { return sizeof(dmiptex_t) + width * height / 64 * 85; } + + virtual void stream_read(std::istream &stream) + { + auto start = stream.tellg(); + + dmiptex_t dtex; + stream >= dtex; + + name = dtex.name.data(); + + width = dtex.width; + height = dtex.height; + + for (size_t g = 0; g < MIPLEVELS; g++) { + if (dtex.offsets[g] <= 0) { + continue; + } + + stream.seekg(static_cast(start) + dtex.offsets[g]); + const size_t num_bytes = (dtex.width >> g) * (dtex.height >> g); + uint8_t *bytes = new uint8_t[num_bytes]; + stream.read(reinterpret_cast(bytes), num_bytes); + data[g] = std::unique_ptr(bytes); + } + } + + virtual void stream_write(std::ostream &stream) const + { + std::array as_array{}; + memcpy(as_array.data(), name.c_str(), name.size()); + + stream <= as_array <= width <= height; + + uint32_t header_end = sizeof(dmiptex_t); + + for (size_t i = 0; i < MIPLEVELS; i++) { + if (data[i] <= 0) { + stream <= (uint32_t)0; + } else { + stream <= header_end; + header_end += (width >> i) * (height >> i); + } + } + + for (size_t i = 0; i < MIPLEVELS; i++) { + if (data[i]) { + stream.write(reinterpret_cast(data[i].get()), (width >> i) * (height >> i)); + } + } + } +}; + +// Half Life miptex, which includes a palette +struct miptexhl_t : miptex_t +{ + std::vector palette; + + miptexhl_t() = default; + + // convert miptex_t to miptexhl_t + miptexhl_t(const miptex_t ©) : miptex_t(copy) { } + + virtual size_t stream_size() const + { + return miptex_t::stream_size() + sizeof(uint16_t) + (palette.size() * sizeof(qvec3b)); + } + + virtual void stream_read(std::istream &stream) + { + miptex_t::stream_read(stream); + + uint16_t num_colors; + stream >= num_colors; + + palette.resize(num_colors); + stream.read(reinterpret_cast(palette.data()), palette.size()); + } + + virtual void stream_write(std::ostream &stream) const + { + miptex_t::stream_write(stream); + + stream <= static_cast(palette.size()); + + stream.write(reinterpret_cast(palette.data()), palette.size()); + } +}; + +// structured miptex container lump +template +struct dmiptexlump_t +{ + std::vector textures; + + dmiptexlump_t() = default; + + // copy from a different lump type + template + dmiptexlump_t(const dmiptexlump_t ©) + { + textures.reserve(copy.textures.size()); + + for (auto &m : copy.textures) { + textures.emplace_back(m); + } + } + + void stream_read(std::istream &stream, const lump_t &lump) + { + int32_t nummiptex; + stream >= nummiptex; + + for (size_t i = 0; i < nummiptex; i++) { + int32_t mipofs; + + stream >= mipofs; + + miptex_t &tex = textures.emplace_back(); + + if (mipofs < 0) + continue; + + auto pos = stream.tellg(); + + stream.seekg(lump.fileofs + mipofs); + + tex.stream_read(stream); + + stream.seekg(pos); + } + } + + void stream_write(std::ostream &stream) const + { + auto p = (size_t)stream.tellp(); + + stream <= static_cast(textures.size()); + + const size_t header_size = sizeof(int32_t) + (sizeof(int32_t) * textures.size()); + + size_t miptex_offset = 0; + + for (auto &texture : textures) { + if (!texture.name[0]) { + stream <= static_cast(-1); + continue; + } + stream <= static_cast(header_size + miptex_offset); + + miptex_offset += texture.stream_size(); + + if ((p + miptex_offset) % 4) { + miptex_offset += 4 - ((p + miptex_offset) % 4); + } + } + + for (auto &texture : textures) { + if (texture.name[0]) { + if (stream.tellp() % 4) { + constexpr const char pad[4]{}; + stream.write(pad, 4 - (stream.tellp() % 4)); + } + texture.stream_write(stream); + } + } + } +}; + +// 0-2 are axial planes +// 3-5 are non-axial planes snapped to the nearest +enum class plane_type_t +{ + PLANE_INVALID = -1, + PLANE_X = 0, + PLANE_Y = 1, + PLANE_Z = 2, + PLANE_ANYX = 3, + PLANE_ANYY = 4, + PLANE_ANYZ = 5, +}; + +struct dplane_t : qplane3f +{ + int32_t type; + + [[nodiscard]] constexpr dplane_t operator-() const { return {qplane3f::operator-(), type}; } + + // serialize for streams + auto stream_data() { return std::tie(normal, dist, type); } + + // optimized case + template + inline T distance_to_fast(const qvec &point) const + { + switch (static_cast(type)) { + case plane_type_t::PLANE_X: return point[0] - dist; + case plane_type_t::PLANE_Y: return point[1] - dist; + case plane_type_t::PLANE_Z: return point[2] - dist; + default: { + return qplane3f::distance_to(point); + } + } + } +}; + +struct bsp2_dnode_t +{ + int32_t planenum; + std::array children; /* negative numbers are -(leafs+1), not nodes */ + qvec3f mins; /* for sphere culling */ + qvec3f maxs; + uint32_t firstface; + uint32_t numfaces; /* counting both sides */ + + // serialize for streams + auto stream_data() { return std::tie(planenum, children, mins, maxs, firstface, numfaces); } +}; + +struct mtexinfo_t +{ + texvecf vecs; // [s/t][xyz offset] + surfflags_t flags; // native miptex flags + extended flags + + // q1 only + int32_t miptex; + + // q2 only + int32_t value; // light emission, etc + std::array texture; // texture name (textures/*.wal) + int32_t nexttexinfo = -1; // for animations, -1 = end of chain +}; + +constexpr size_t MAXLIGHTMAPS = 4; + +struct mface_t +{ + int64_t planenum; + int32_t side; // if true, the face is on the back side of the plane + int32_t firstedge; /* we must support > 64k edges */ + int32_t numedges; + int32_t texinfo; + + /* lighting info */ + std::array styles; + int32_t lightofs; /* start of [numstyles*surfsize] samples */ + + // serialize for streams + auto stream_data() { return std::tie(planenum, side, firstedge, numedges, texinfo, styles, lightofs); } +}; + +/* + * Note that children are interpreted as unsigned values now, so that we can + * handle > 32k clipnodes. Values > 0xFFF0 can be assumed to be CONTENTS + * values and can be read as the signed value to be compatible with the above + * (i.e. simply subtract 65536). + */ +struct bsp2_dclipnode_t +{ + int32_t planenum; + std::array children; /* negative numbers are contents */ + + // serialize for streams + auto stream_data() { return std::tie(planenum, children); } +}; + +using bsp2_dedge_t = std::array; /* vertex numbers */ + +/* + * leaf 0 is the generic CONTENTS_SOLID leaf, used for all solid areas (except Q2) + * all other leafs need visibility info + */ +/* Ambient Sounds */ +enum ambient_type_t : uint8_t +{ + AMBIENT_WATER, + AMBIENT_SKY, + AMBIENT_SLIME, + AMBIENT_LAVA, + + NUM_AMBIENTS = 4 +}; + +struct mleaf_t +{ + // bsp2_dleaf_t + int32_t contents; + int32_t visofs; /* -1 = no visibility info */ + qvec3f mins; /* for frustum culling */ + qvec3f maxs; + uint32_t firstmarksurface; + uint32_t nummarksurfaces; + std::array ambient_level; + + // q2 extras + int32_t cluster; + int32_t area; + uint32_t firstleafbrush; + uint32_t numleafbrushes; +}; + +struct darea_t +{ + int32_t numareaportals; + int32_t firstareaportal; + + // serialize for streams + auto stream_data() { return std::tie(numareaportals, firstareaportal); } + auto tuple() const { return std::tie(numareaportals, firstareaportal); } + + // comparison operator for tests + bool operator==(const darea_t &other) const { return tuple() == other.tuple(); } +}; + +// each area has a list of portals that lead into other areas +// when portals are closed, other areas may not be visible or +// hearable even if the vis info says that it should be +struct dareaportal_t +{ + int32_t portalnum; + int32_t otherarea; + + // serialize for streams + auto stream_data() { return std::tie(portalnum, otherarea); } + auto tuple() const { return std::tie(portalnum, otherarea); } + + // comparison operator for tests + bool operator==(const dareaportal_t &other) const { return tuple() == other.tuple(); } +}; + +struct dbrush_t +{ + int32_t firstside; + int32_t numsides; + int32_t contents; + + // serialize for streams + auto stream_data() { return std::tie(firstside, numsides, contents); } +}; + +struct q2_dbrushside_qbism_t +{ + uint32_t planenum; // facing out of the leaf + int32_t texinfo; + + // serialize for streams + auto stream_data() { return std::tie(planenum, texinfo); } +}; + +struct bspversion_t; + + // "generic" bsp - superset of all other supported types +struct mbsp_t +{ + // the BSP version that we came from, if any + const bspversion_t *loadversion; + + std::vector dmodels; + mvis_t dvis; + std::vector dlightdata; + dmiptexlump_t dtex; + std::string dentdata; + std::vector dleafs; + std::vector dplanes; + std::vector dvertexes; + std::vector dnodes; + std::vector texinfo; + std::vector dfaces; + std::vector dclipnodes; + std::vector dedges; + std::vector dleaffaces; + std::vector dleafbrushes; + std::vector dsurfedges; + std::vector dareas; + std::vector dareaportals; + std::vector dbrushes; + std::vector dbrushsides; +}; + +extern const bspversion_t bspver_generic; \ No newline at end of file diff --git a/include/common/bspfile_q1.hh b/include/common/bspfile_q1.hh new file mode 100644 index 00000000..c09d942d --- /dev/null +++ b/include/common/bspfile_q1.hh @@ -0,0 +1,488 @@ +/* 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 +#include +#include +#include +#include +#include "qvec.hh" + +constexpr int32_t BSPVERSION = 29; +constexpr int32_t BSP2RMQVERSION = (('B' << 24) | ('S' << 16) | ('P' << 8) | '2'); +constexpr int32_t BSP2VERSION = ('B' | ('S' << 8) | ('P' << 16) | ('2' << 24)); +constexpr int32_t BSPHLVERSION = 30; // 24bit lighting, and private palettes in the textures lump. + +enum q1_lump_t +{ + LUMP_ENTITIES, + LUMP_PLANES, + LUMP_TEXTURES, + LUMP_VERTEXES, + LUMP_VISIBILITY, + LUMP_NODES, + LUMP_TEXINFO, + LUMP_FACES, + LUMP_LIGHTING, + LUMP_CLIPNODES, + LUMP_LEAFS, + LUMP_MARKSURFACES, + LUMP_EDGES, + LUMP_SURFEDGES, + LUMP_MODELS, + + BSP_LUMPS +}; + +struct dheader_t +{ + int32_t ident; + std::array lumps; + + auto stream_data() { return std::tie(ident, lumps); } +}; + +constexpr size_t MAX_MAP_HULLS_Q1 = 4; + +struct dmodelq1_t +{ + qvec3f mins; + qvec3f maxs; + qvec3f origin; + std::array headnode; /* 4 for backward compat, only 3 hulls exist */ + int32_t visleafs; /* not including the solid leaf 0 */ + int32_t firstface; + int32_t numfaces; + + dmodelq1_t() = default; + + // convert from mbsp_t + dmodelq1_t(const dmodelh2_t &model) + : mins(model.mins), maxs(model.maxs), origin(model.origin), + headnode(array_cast(model.headnode, "dmodelh2_t::headnode")), visleafs(model.visleafs), + firstface(model.firstface), numfaces(model.numfaces) + { + } + + // convert to mbsp_t + operator dmodelh2_t() const + { + return {mins, maxs, origin, array_cast(headnode, "dmodelh2_t::headnode"), + visleafs, firstface, numfaces}; + } + + // serialize for streams + auto stream_data() { return std::tie(mins, maxs, origin, headnode, visleafs, firstface, numfaces); } +}; + +// Q1 contents + +enum q1_contents_t : int32_t +{ + CONTENTS_EMPTY = -1, + CONTENTS_SOLID = -2, + CONTENTS_WATER = -3, + CONTENTS_SLIME = -4, + CONTENTS_LAVA = -5, + CONTENTS_SKY = -6, + + CONTENTS_MIN = CONTENTS_SKY +}; + +struct bsp29_dnode_t +{ + int32_t planenum; + std::array + children; /* negative numbers are -(leafs+1), not nodes. children[0] is front, children[1] is back */ + qvec3s mins; /* for sphere culling */ + qvec3s maxs; + uint16_t firstface; + uint16_t numfaces; /* counting both sides */ + + bsp29_dnode_t() = default; + + // convert from mbsp_t + bsp29_dnode_t(const bsp2_dnode_t &model) + : planenum(model.planenum), children(array_cast(model.children, "dnode_t::children")), + mins(aabb_mins_cast(model.mins, "dnode_t::mins")), + maxs(aabb_maxs_cast(model.maxs, "dnode_t::maxs")), + firstface(numeric_cast(model.firstface, "dnode_t::firstface")), + numfaces(numeric_cast(model.numfaces, "dnode_t::numfaces")) + { + } + + // convert to mbsp_t + operator bsp2_dnode_t() const + { + return {planenum, array_cast(children, "dnode_t::children"), + aabb_mins_cast(mins, "dnode_t::mins"), aabb_mins_cast(maxs, "dnode_t::maxs"), firstface, + numfaces}; + } + + // serialize for streams + auto stream_data() { return std::tie(planenum, children, mins, maxs, firstface, numfaces); } +}; + +struct bsp2rmq_dnode_t +{ + int32_t planenum; + std::array children; /* negative numbers are -(leafs+1), not nodes */ + qvec3s mins; /* for sphere culling */ + qvec3s maxs; + uint32_t firstface; + uint32_t numfaces; /* counting both sides */ + + bsp2rmq_dnode_t() = default; + + // convert from mbsp_t + bsp2rmq_dnode_t(const bsp2_dnode_t &model) + : planenum(model.planenum), children(model.children), + mins(aabb_mins_cast(model.mins, "dnode_t::mins")), + maxs(aabb_maxs_cast(model.maxs, "dnode_t::maxs")), firstface(model.firstface), + numfaces(model.numfaces) + { + } + + // convert to mbsp_t + operator bsp2_dnode_t() const + { + return {planenum, children, aabb_mins_cast(mins, "dnode_t::mins"), + aabb_mins_cast(maxs, "dnode_t::maxs"), firstface, numfaces}; + } + + // serialize for streams + auto stream_data() { return std::tie(planenum, children, mins, maxs, firstface, numfaces); } +}; + +/* +* Clipnodes need to be stored as a 16-bit offset. Originally, this was a +* signed value and only the positive values up to 32767 were available. Since +* the negative range was unused apart from a few values reserved for flags, +* this has been extended to allow up to 65520 (0xfff0) clipnodes (with a +* suitably modified engine). +*/ +struct bsp29_dclipnode_t +{ + int32_t planenum; + std::array children; /* negative numbers are contents */ + + bsp29_dclipnode_t() = default; + + // convert from mbsp_t + bsp29_dclipnode_t(const bsp2_dclipnode_t &model) + : planenum(model.planenum), children({downcast(model.children[0]), downcast(model.children[1])}) + { + } + + // convert to mbsp_t + operator bsp2_dclipnode_t() const { return {planenum, {upcast(children[0]), upcast(children[1])}}; } + + // serialize for streams + auto stream_data() { return std::tie(planenum, children); } + + /* Slightly tricky since we support > 32k clipnodes */ +private: + static constexpr int16_t downcast(const int32_t &v) + { + if (v < -15 || v > 0xFFF0) + throw std::overflow_error("dclipmode_t::children"); + + return static_cast(v < 0 ? v + 0x10000 : v); + } + + static constexpr int32_t upcast(const int16_t &v) + { + int32_t child = (uint16_t)v; + return child > 0xfff0 ? child - 0x10000 : child; + } +}; + +// Q1 Texture flags. +enum q1_surf_flags_t : int32_t +{ + TEX_SPECIAL = nth_bit(0) /* sky or slime, no lightmap or 256 subdivision */ +}; + +struct texinfo_t +{ + texvecf vecs; /* [s/t][xyz offset] */ + int32_t miptex; + int32_t flags; + + texinfo_t() = default; + + // convert from mbsp_t + texinfo_t(const mtexinfo_t &model) : vecs(model.vecs), miptex(model.miptex), flags(model.flags.native) { } + + // convert to mbsp_t + operator mtexinfo_t() const { return {vecs, {flags}, miptex}; } + + // serialize for streams + auto stream_data() { return std::tie(vecs, miptex, flags); } +}; + +/* + * Note that edge 0 is never used, because negative edge nums are used for + * counterclockwise use of the edge in a face + */ +using bsp29_dedge_t = std::array; /* vertex numbers */ + +struct bsp29_dface_t +{ + int16_t planenum; + int16_t side; + int32_t firstedge; /* we must support > 64k edges */ + int16_t numedges; + int16_t texinfo; + + /* lighting info */ + std::array styles; + int32_t lightofs; /* start of [numstyles*surfsize] samples */ + + bsp29_dface_t() = default; + + // convert from mbsp_t + bsp29_dface_t(const mface_t &model) + : planenum(numeric_cast(model.planenum, "dface_t::planenum")), + side(numeric_cast(model.side, "dface_t::side")), firstedge(model.firstedge), + numedges(numeric_cast(model.numedges, "dface_t::numedges")), + texinfo(numeric_cast(model.texinfo, "dface_t::texinfo")), styles(model.styles), + lightofs(model.lightofs) + { + } + + // convert to mbsp_t + operator mface_t() const { return {planenum, side, firstedge, numedges, texinfo, styles, lightofs}; } + + // serialize for streams + auto stream_data() { return std::tie(planenum, side, firstedge, numedges, texinfo, styles, lightofs); } +}; + +struct bsp2_dface_t +{ + int32_t planenum; + int32_t side; // if true, the face is on the back side of the plane + int32_t firstedge; /* we must support > 64k edges */ + int32_t numedges; + int32_t texinfo; + + /* lighting info */ + std::array styles; + int32_t lightofs; /* start of [numstyles*surfsize] samples */ + + bsp2_dface_t() = default; + + // convert from mbsp_t + bsp2_dface_t(const mface_t &model) + : planenum(numeric_cast(model.planenum, "dface_t::planenum")), side(model.side), + firstedge(model.firstedge), numedges(model.numedges), texinfo(model.texinfo), styles(model.styles), + lightofs(model.lightofs) + { + } + + // convert to mbsp_t + operator mface_t() const { return {planenum, side, firstedge, numedges, texinfo, styles, lightofs}; } + + // serialize for streams + auto stream_data() { return std::tie(planenum, side, firstedge, numedges, texinfo, styles, lightofs); } +}; + +struct bsp29_dleaf_t +{ + int32_t contents; + int32_t visofs; /* -1 = no visibility info */ + qvec3s mins; /* for frustum culling */ + qvec3s maxs; + uint16_t firstmarksurface; + uint16_t nummarksurfaces; + std::array ambient_level; + + bsp29_dleaf_t() = default; + + // convert from mbsp_t + bsp29_dleaf_t(const mleaf_t &model) + : contents(model.contents), visofs(model.visofs), mins(aabb_mins_cast(model.mins, "dleaf_t::mins")), + maxs(aabb_maxs_cast(model.maxs, "dleaf_t::maxs")), + firstmarksurface(numeric_cast(model.firstmarksurface, "dleaf_t::firstmarksurface")), + nummarksurfaces(numeric_cast(model.nummarksurfaces, "dleaf_t::nummarksurfaces")), + ambient_level(model.ambient_level) + { + } + + // convert to mbsp_t + operator mleaf_t() const + { + return {contents, visofs, aabb_mins_cast(mins, "dleaf_t::mins"), + aabb_mins_cast(maxs, "dleaf_t::maxs"), firstmarksurface, nummarksurfaces, ambient_level}; + } + + // serialize for streams + auto stream_data() + { + return std::tie(contents, visofs, mins, maxs, firstmarksurface, nummarksurfaces, ambient_level); + } +}; + +struct bsp2rmq_dleaf_t +{ + int32_t contents; + int32_t visofs; /* -1 = no visibility info */ + qvec3s mins; /* for frustum culling */ + qvec3s maxs; + uint32_t firstmarksurface; + uint32_t nummarksurfaces; + std::array ambient_level; + + bsp2rmq_dleaf_t() = default; + + // convert from mbsp_t + bsp2rmq_dleaf_t(const mleaf_t &model) + : contents(model.contents), visofs(model.visofs), mins(aabb_mins_cast(model.mins, "dleaf_t::mins")), + maxs(aabb_maxs_cast(model.maxs, "dleaf_t::maxs")), firstmarksurface(model.firstmarksurface), + nummarksurfaces(model.nummarksurfaces), ambient_level(model.ambient_level) + { + } + + // convert to mbsp_t + operator mleaf_t() const + { + return {contents, visofs, aabb_mins_cast(mins, "dleaf_t::mins"), + aabb_mins_cast(maxs, "dleaf_t::maxs"), firstmarksurface, nummarksurfaces, ambient_level}; + } + + // serialize for streams + auto stream_data() + { + return std::tie(contents, visofs, mins, maxs, firstmarksurface, nummarksurfaces, ambient_level); + } +}; + +struct bsp2_dleaf_t +{ + int32_t contents; + int32_t visofs; /* -1 = no visibility info */ + qvec3f mins; /* for frustum culling */ + qvec3f maxs; + uint32_t firstmarksurface; + uint32_t nummarksurfaces; + std::array ambient_level; + + bsp2_dleaf_t() = default; + + // convert from mbsp_t + bsp2_dleaf_t(const mleaf_t &model) + : contents(model.contents), visofs(model.visofs), mins(model.mins), maxs(model.maxs), + firstmarksurface(model.firstmarksurface), nummarksurfaces(model.nummarksurfaces), + ambient_level(model.ambient_level) + { + } + + // convert to mbsp_t + operator mleaf_t() const + { + return {contents, visofs, mins, maxs, firstmarksurface, nummarksurfaces, ambient_level}; + } + + // serialize for streams + auto stream_data() + { + return std::tie(contents, visofs, mins, maxs, firstmarksurface, nummarksurfaces, ambient_level); + } +}; + + +// Q1-esque maps can use one of these two. +using dmodelq1_vector = std::vector; +using dmodelh2_vector = std::vector; + +// Q1-esque maps can use one of these two. +using miptexq1_lump = dmiptexlump_t; +using miptexhl_lump = dmiptexlump_t; + +// type tag used for type inference +struct q1bsp_tag_t +{ +}; + +struct bsp29_t : q1bsp_tag_t +{ + std::variant dmodels; + std::vector dvisdata; + std::vector dlightdata; + std::variant dtex; + std::string dentdata; + std::vector dleafs; + std::vector dplanes; + std::vector dvertexes; + std::vector dnodes; + std::vector texinfo; + std::vector dfaces; + std::vector dclipnodes; + std::vector dedges; + std::vector dmarksurfaces; + std::vector dsurfedges; +}; + +struct bsp2rmq_t : q1bsp_tag_t +{ + std::variant dmodels; + std::vector dvisdata; + std::vector dlightdata; + std::variant dtex; + std::string dentdata; + std::vector dleafs; + std::vector dplanes; + std::vector dvertexes; + std::vector dnodes; + std::vector texinfo; + std::vector dfaces; + std::vector dclipnodes; + std::vector dedges; + std::vector dmarksurfaces; + std::vector dsurfedges; +}; + +struct bsp2_t : q1bsp_tag_t +{ + std::variant dmodels; + std::vector dvisdata; + std::vector dlightdata; + std::variant dtex; + std::string dentdata; + std::vector dleafs; + std::vector dplanes; + std::vector dvertexes; + std::vector dnodes; + std::vector texinfo; + std::vector dfaces; + std::vector dclipnodes; + std::vector dedges; + std::vector dmarksurfaces; + std::vector dsurfedges; +}; + +extern const bspversion_t bspver_q1; +extern const bspversion_t bspver_h2; +extern const bspversion_t bspver_h2bsp2; +extern const bspversion_t bspver_h2bsp2rmq; +extern const bspversion_t bspver_bsp2; +extern const bspversion_t bspver_bsp2rmq; +extern const bspversion_t bspver_hl; \ No newline at end of file diff --git a/include/common/bspfile_q2.hh b/include/common/bspfile_q2.hh new file mode 100644 index 00000000..451e62dd --- /dev/null +++ b/include/common/bspfile_q2.hh @@ -0,0 +1,445 @@ +/* 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 +#include +#include +#include +#include "qvec.hh" + +constexpr int32_t Q2_BSPVERSION = 38; +constexpr int32_t Q2_BSPIDENT = (('P' << 24) + ('S' << 16) + ('B' << 8) + 'I'); +constexpr int32_t Q2_QBISMIDENT = (('P' << 24) + ('S' << 16) + ('B' << 8) + 'Q'); + +enum q2_lump_t +{ + Q2_LUMP_ENTITIES, + Q2_LUMP_PLANES, + Q2_LUMP_VERTEXES, + Q2_LUMP_VISIBILITY, + Q2_LUMP_NODES, + Q2_LUMP_TEXINFO, + Q2_LUMP_FACES, + Q2_LUMP_LIGHTING, + Q2_LUMP_LEAFS, + Q2_LUMP_LEAFFACES, + Q2_LUMP_LEAFBRUSHES, + Q2_LUMP_EDGES, + Q2_LUMP_SURFEDGES, + Q2_LUMP_MODELS, + Q2_LUMP_BRUSHES, + Q2_LUMP_BRUSHSIDES, + Q2_LUMP_POP, + Q2_LUMP_AREAS, + Q2_LUMP_AREAPORTALS, + + Q2_HEADER_LUMPS +}; + +struct q2_dheader_t +{ + int32_t ident; + int32_t version; + std::array lumps; + + auto stream_data() { return std::tie(ident, version, lumps); } +}; + +struct q2_dmodel_t +{ + qvec3f mins; + qvec3f maxs; + qvec3f origin; // for sounds or lights + int32_t headnode; + int32_t firstface; + int32_t numfaces; // submodels just draw faces + // without walking the bsp tree + + q2_dmodel_t() = default; + + // convert from mbsp_t + q2_dmodel_t(const dmodelh2_t &model) + : mins(model.mins), maxs(model.maxs), origin(model.origin), headnode(model.headnode[0]), + firstface(model.firstface), numfaces(model.numfaces) + { + } + + // convert to mbsp_t + operator dmodelh2_t() const + { + return {mins, maxs, origin, {headnode}, + 0, // visleafs + firstface, numfaces}; + } + + // serialize for streams + auto stream_data() { return std::tie(mins, maxs, origin, headnode, firstface, numfaces); } +}; + + +// Q2 contents (from qfiles.h) + +// contents flags are seperate bits +// a given brush can contribute multiple content bits +// multiple brushes can be in a single leaf + +// lower bits are stronger, and will eat weaker brushes completely +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_WINDOW = nth_bit(1), // translucent, but not watery + Q2_CONTENTS_AUX = nth_bit(2), + Q2_CONTENTS_LAVA = nth_bit(3), + Q2_CONTENTS_SLIME = nth_bit(4), + Q2_CONTENTS_WATER = nth_bit(5), + Q2_CONTENTS_MIST = nth_bit(6), + Q2_LAST_VISIBLE_CONTENTS = Q2_CONTENTS_MIST, + 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_LIQUID = (Q2_CONTENTS_LAVA | Q2_CONTENTS_SLIME | Q2_CONTENTS_WATER), // mxd + + // remaining contents are non-visible, and don't eat brushes + + Q2_CONTENTS_AREAPORTAL = nth_bit(15), + + Q2_CONTENTS_PLAYERCLIP = nth_bit(16), + Q2_CONTENTS_MONSTERCLIP = nth_bit(17), + + // currents can be added to any other contents, and may be mixed + Q2_CONTENTS_CURRENT_0 = nth_bit(18), + Q2_CONTENTS_CURRENT_90 = nth_bit(19), + Q2_CONTENTS_CURRENT_180 = nth_bit(20), + Q2_CONTENTS_CURRENT_270 = nth_bit(21), + Q2_CONTENTS_CURRENT_UP = nth_bit(22), + Q2_CONTENTS_CURRENT_DOWN = nth_bit(23), + + Q2_CONTENTS_ORIGIN = nth_bit(24), // removed before bsping an entity + + Q2_CONTENTS_MONSTER = nth_bit(25), // should never be on a brush, only in game + Q2_CONTENTS_DEADMONSTER = nth_bit(26), + Q2_CONTENTS_DETAIL = nth_bit(27), // brushes to be added after vis leafs + Q2_CONTENTS_TRANSLUCENT = nth_bit(28), // auto set if any surface has trans + Q2_CONTENTS_LADDER = nth_bit(29) +}; + +struct q2_dnode_t +{ + int32_t planenum; + std::array children; // negative numbers are -(leafs+1), not nodes + qvec3s mins; // for frustom culling + qvec3s maxs; + uint16_t firstface; + uint16_t numfaces; // counting both sides + + q2_dnode_t() = default; + + // convert from mbsp_t + q2_dnode_t(const bsp2_dnode_t &model) + : planenum(model.planenum), children(model.children), + mins(aabb_mins_cast(model.mins, "dnode_t::mins")), + maxs(aabb_maxs_cast(model.maxs, "dnode_t::maxs")), + firstface(numeric_cast(model.firstface, "dnode_t::firstface")), + numfaces(numeric_cast(model.numfaces, "dnode_t::numfaces")) + { + } + + // convert to mbsp_t + operator bsp2_dnode_t() const + { + return {planenum, children, aabb_mins_cast(mins, "dnode_t::mins"), + aabb_mins_cast(maxs, "dnode_t::maxs"), firstface, numfaces}; + } + + // serialize for streams + auto stream_data() { return std::tie(planenum, children, mins, maxs, firstface, numfaces); } +}; + +using q2_dnode_qbism_t = bsp2_dnode_t; + +// Q2 Texture flags. +enum q2_surf_flags_t : int32_t +{ + Q2_SURF_LIGHT = nth_bit(0), // value will hold the light strength + + Q2_SURF_SLICK = nth_bit(1), // effects game physics + + Q2_SURF_SKY = nth_bit(2), // don't draw, but add to skybox + Q2_SURF_WARP = nth_bit(3), // turbulent water warp + Q2_SURF_TRANS33 = nth_bit(4), + Q2_SURF_TRANS66 = nth_bit(5), + Q2_SURF_FLOWING = nth_bit(6), // scroll towards angle + Q2_SURF_NODRAW = nth_bit(7), // don't bother referencing the texture + + Q2_SURF_HINT = nth_bit(8), // make a primary bsp splitter + Q2_SURF_SKIP = nth_bit(9), // ONLY FOR HINT! "nodraw" = Q1 "skip" + + Q2_SURF_TRANSLUCENT = (Q2_SURF_TRANS33 | Q2_SURF_TRANS66), // mxd +}; + +struct q2_texinfo_t +{ + texvecf vecs; // [s/t][xyz offset] + int32_t flags; // miptex flags + overrides + int32_t value; // light emission, etc + std::array texture; // texture name (textures/*.wal) + int32_t nexttexinfo; // for animations, -1 = end of chain + + q2_texinfo_t() = default; + + // convert from mbsp_t + q2_texinfo_t(const mtexinfo_t &model) + : vecs(model.vecs), flags(model.flags.native), value(model.value), texture(model.texture), + nexttexinfo(model.nexttexinfo) + { + } + + // convert to mbsp_t + operator mtexinfo_t() const { return {vecs, {flags}, -1, value, texture, nexttexinfo}; } + + // serialize for streams + auto stream_data() { return std::tie(vecs, flags, value, texture, nexttexinfo); } +}; + +struct q2_dface_t +{ + uint16_t planenum; // NOTE: only difference from bsp29_dface_t + int16_t side; + int32_t firstedge; // we must support > 64k edges + int16_t numedges; + int16_t texinfo; + + // lighting info + std::array styles; + int32_t lightofs; // start of [numstyles*surfsize] samples + + q2_dface_t() = default; + + // convert from mbsp_t + q2_dface_t(const mface_t &model) + : planenum(numeric_cast(model.planenum, "dface_t::planenum")), + side(numeric_cast(model.side, "dface_t::side")), firstedge(model.firstedge), + numedges(numeric_cast(model.numedges, "dface_t::numedges")), + texinfo(numeric_cast(model.texinfo, "dface_t::texinfo")), styles(model.styles), + lightofs(model.lightofs) + { + } + + // convert to mbsp_t + operator mface_t() const { return {planenum, side, firstedge, numedges, texinfo, styles, lightofs}; } + + // serialize for streams + auto stream_data() { return std::tie(planenum, side, firstedge, numedges, texinfo, styles, lightofs); } +}; + +struct q2_dface_qbism_t +{ + uint32_t planenum; // NOTE: only difference from bsp2_dface_t + int32_t side; + int32_t firstedge; // we must support > 64k edges + int32_t numedges; + int32_t texinfo; + + // lighting info + std::array styles; + int32_t lightofs; // start of [numstyles*surfsize] samples + + q2_dface_qbism_t() = default; + + // convert from mbsp_t + q2_dface_qbism_t(const mface_t &model) + : planenum(numeric_cast(model.planenum, "dface_t::planenum")), side(model.side), + firstedge(model.firstedge), numedges(model.numedges), texinfo(model.texinfo), styles(model.styles), + lightofs(model.lightofs) + { + } + + // convert to mbsp_t + operator mface_t() const { return {planenum, side, firstedge, numedges, texinfo, styles, lightofs}; } + + // serialize for streams + auto stream_data() { return std::tie(planenum, side, firstedge, numedges, texinfo, styles, lightofs); } +}; + + +struct q2_dleaf_t +{ + int32_t contents; // OR of all brushes (not needed?) + + int16_t cluster; + int16_t area; + + qvec3s mins; // for frustum culling + qvec3s maxs; + + uint16_t firstleafface; + uint16_t numleaffaces; + + uint16_t firstleafbrush; + uint16_t numleafbrushes; + + q2_dleaf_t() = default; + + // convert from mbsp_t + q2_dleaf_t(const mleaf_t &model) + : contents(model.contents), cluster(numeric_cast(model.cluster, "dleaf_t::cluster")), + area(numeric_cast(model.area, "dleaf_t::area")), + mins(aabb_mins_cast(model.mins, "dleaf_t::mins")), + maxs(aabb_mins_cast(model.maxs, "dleaf_t::maxs")), + firstleafface(numeric_cast(model.firstmarksurface, "dleaf_t::firstmarksurface")), + numleaffaces(numeric_cast(model.nummarksurfaces, "dleaf_t::nummarksurfaces")), + firstleafbrush(numeric_cast(model.firstleafbrush, "dleaf_t::firstleafbrush")), + numleafbrushes(numeric_cast(model.numleafbrushes, "dleaf_t::numleafbrushes")) + { + } + + // convert to mbsp_t + operator mleaf_t() const + { + return {contents, -1, aabb_mins_cast(mins, "dleaf_t::mins"), + aabb_mins_cast(maxs, "dleaf_t::maxs"), firstleafface, numleaffaces, {}, cluster, area, + firstleafbrush, numleafbrushes}; + } + + // serialize for streams + auto stream_data() + { + return std::tie( + contents, cluster, area, mins, maxs, firstleafface, numleaffaces, firstleafbrush, numleafbrushes); + } +}; + +struct q2_dleaf_qbism_t +{ + int32_t contents; // OR of all brushes (not needed?) + + int32_t cluster; + int32_t area; + + qvec3f mins; // for frustum culling + qvec3f maxs; + + uint32_t firstleafface; + uint32_t numleaffaces; + + uint32_t firstleafbrush; + uint32_t numleafbrushes; + + q2_dleaf_qbism_t() = default; + + // convert from mbsp_t + q2_dleaf_qbism_t(const mleaf_t &model) + : contents(model.contents), cluster(model.cluster), area(model.area), mins(model.mins), maxs(model.maxs), + firstleafface(model.firstmarksurface), numleaffaces(model.nummarksurfaces), + firstleafbrush(model.firstleafbrush), numleafbrushes(model.numleafbrushes) + { + } + + // convert to mbsp_t + operator mleaf_t() const + { + return { + contents, -1, mins, maxs, firstleafface, numleaffaces, {}, cluster, area, firstleafbrush, numleafbrushes}; + } + + // serialize for streams + auto stream_data() + { + return std::tie( + contents, cluster, area, mins, maxs, firstleafface, numleaffaces, firstleafbrush, numleafbrushes); + } +}; + +struct q2_dbrushside_t +{ + uint16_t planenum; // facing out of the leaf + int16_t texinfo; + + q2_dbrushside_t() = default; + + // convert from mbsp_t + q2_dbrushside_t(const q2_dbrushside_qbism_t &model) + : planenum(numeric_cast(model.planenum, "dbrushside_t::planenum")), + texinfo(numeric_cast(model.texinfo, "dbrushside_t::texinfo")) + { + } + + // convert to mbsp_t + operator q2_dbrushside_qbism_t() const { return {planenum, texinfo}; } + + // serialize for streams + auto stream_data() { return std::tie(planenum, texinfo); } +}; + +// type tag used for type inference +struct q2bsp_tag_t +{ +}; + +struct q2bsp_t : q2bsp_tag_t +{ + std::vector dmodels; + + mvis_t dvis; + + std::vector dlightdata; + std::string dentdata; + std::vector dleafs; + std::vector dplanes; + std::vector dvertexes; + std::vector dnodes; + std::vector texinfo; + std::vector dfaces; + std::vector dedges; + std::vector dleaffaces; + std::vector dleafbrushes; + std::vector dsurfedges; + std::vector dareas; + std::vector dareaportals; + std::vector dbrushes; + std::vector dbrushsides; +}; + +struct q2bsp_qbism_t : q2bsp_tag_t +{ + std::vector dmodels; + mvis_t dvis; + std::vector dlightdata; + std::string dentdata; + std::vector dleafs; + std::vector dplanes; + std::vector dvertexes; + std::vector dnodes; + std::vector texinfo; + std::vector dfaces; + std::vector dedges; + std::vector dleaffaces; + std::vector dleafbrushes; + std::vector dsurfedges; + std::vector dareas; + std::vector dareaportals; + std::vector dbrushes; + std::vector dbrushsides; +}; + +extern const bspversion_t bspver_q2; +extern const bspversion_t bspver_qbism; \ No newline at end of file diff --git a/include/common/qvec.hh b/include/common/qvec.hh index 1ed37b6c..51b429c4 100644 --- a/include/common/qvec.hh +++ b/include/common/qvec.hh @@ -706,7 +706,7 @@ public: // generic case template - inline F distance_to(const qvec &point) const + [[nodiscard]] inline F distance_to(const qvec &point) const { return qv::dot(point, normal) - dist; }