q2 and bug fixes

# Conflicts:
#	include/qbsp/qbsp.hh
#	include/qbsp/winding.hh
#	qbsp/brush.cc
#	qbsp/csg4.cc
#	qbsp/merge.cc
This commit is contained in:
Jonathan 2021-09-12 08:30:45 -04:00
parent 8cdb9ff6c4
commit c95a1e2ccb
20 changed files with 473 additions and 192 deletions

View File

@ -8,5 +8,5 @@ set(BSPINFO_SOURCES
${COMMON_INCLUDES})
add_executable(bspinfo ${BSPINFO_SOURCES})
target_link_libraries(bspinfo ${CMAKE_THREAD_LIBS_INIT})
target_link_libraries(bspinfo ${CMAKE_THREAD_LIBS_INIT} fmt::fmt)
install(TARGETS bspinfo RUNTIME DESTINATION bin)

View File

@ -21,6 +21,39 @@
#include <common/cmdlib.hh>
#include <common/bspfile.hh>
inline void PrintBSPInfo(const bspdata_t &bsp) {
printf("brushes:\n");
for (int32_t i = 0; i < bsp.data.q2bsp.numbrushes; i++) {
printf(" %i: contents: %i, num sides: %i, first side: %i\n", i, bsp.data.q2bsp.dbrushes[i].contents, bsp.data.q2bsp.dbrushes[i].numsides, bsp.data.q2bsp.dbrushes[i].firstside);
}
printf("brush sides:\n");
for (int32_t i = 0; i < bsp.data.q2bsp.numbrushsides; i++) {
auto &plane = bsp.data.q2bsp.dplanes[bsp.data.q2bsp.dbrushsides[i].planenum];
printf(" %i: { %i: %f %f %f -> %f }\n", i, plane.type, plane.normal[0], plane.normal[1], plane.normal[2], plane.dist);
}
printf("leaves:\n");
for (int32_t i = 0; i < bsp.data.q2bsp.numleafs; i++) {
auto &leaf = bsp.data.q2bsp.dleafs[i];
printf(" %i: contents %i, leafbrushes first %i -> count %i\n", i, leaf.contents, leaf.firstleafbrush, leaf.numleafbrushes);
}
printf("nodes:\n");
for (int32_t i = 0; i < bsp.data.q2bsp.numnodes; i++) {
auto &node = bsp.data.q2bsp.dnodes[i];
auto &plane = bsp.data.q2bsp.dplanes[node.planenum];
printf(" %i: { %i: %f %f %f -> %f }\n", i, plane.type, plane.normal[0], plane.normal[1], plane.normal[2], plane.dist);
}
printf("models:\n");
for (int32_t i = 0; i < bsp.data.q2bsp.nummodels; i++) {
auto &model = bsp.data.q2bsp.dmodels[i];
printf(" %i: headnode %i (%f %f %f -> %f %f %f)\n", i, model.headnode, model.mins[0], model.mins[1], model.mins[2], model.maxs[0], model.maxs[1], model.maxs[2]);
}
}
int
main(int argc, char **argv)
{
@ -43,6 +76,8 @@ main(int argc, char **argv)
LoadBSPFile(source, &bsp);
PrintBSPFileSizes(&bsp);
PrintBSPInfo(bsp);
printf("---------------------\n");
}

View File

@ -22,6 +22,8 @@
#include <common/bspfile.hh>
#include <cstdint>
#include <fmt/format.h>
struct gamedef_generic_t : public gamedef_t {
gamedef_generic_t()
{
@ -72,6 +74,14 @@ struct gamedef_generic_t : public gamedef_t {
bool contents_are_valid(const contentflags_t &, bool) const {
throw std::bad_cast();
}
bool portal_can_see_through(const contentflags_t &, const contentflags_t &) const {
throw std::bad_cast();
}
std::string get_contents_display(const contentflags_t &contents) const {
throw std::bad_cast();
}
};
template<gameid_t id>
@ -168,6 +178,33 @@ struct gamedef_q1_like_t : public gamedef_t {
bool contents_are_valid(const contentflags_t &contents, bool strict) const {
return contents.native <= 0;
}
bool portal_can_see_through(const contentflags_t &contents0, const contentflags_t &contents1) const {
/* If contents values are the same and not solid, can see through */
return !(contents0.is_structural_solid(this) || contents1.is_structural_solid(this)) &&
contents0 == contents1;
}
std::string get_contents_display(const contentflags_t &contents) const {
switch (contents.native) {
case 0:
return "UNSET";
case CONTENTS_EMPTY:
return "EMPTY";
case CONTENTS_SOLID:
return "SOLID";
case CONTENTS_SKY:
return "SKY";
case CONTENTS_WATER:
return "WATER";
case CONTENTS_SLIME:
return "SLIME";
case CONTENTS_LAVA:
return "LAVA";
default:
return fmt::to_string(contents.native);
}
}
};
struct gamedef_h2_t : public gamedef_q1_like_t<GAME_HEXEN_II> {
@ -278,10 +315,101 @@ struct gamedef_q2_t : public gamedef_t {
}
bool contents_are_valid(const contentflags_t &contents, bool strict) const {
if (!strict) {
return true;
// check that we don't have more than one visible contents type
const int32_t x = (contents.native & ((Q2_LAST_VISIBLE_CONTENTS << 1) - 1));
if ((x & (x - 1)) != 0) {
return false;
}
return !contents_are_empty(contents);
// TODO: check other invalid mixes
if (!x && strict) {
return false;
}
return true;
}
constexpr int32_t visible_contents(const int32_t &contents) const {
for (int32_t i = 1; i <= Q2_LAST_VISIBLE_CONTENTS; i <<= 1)
if (contents & i )
return i;
return 0;
}
bool portal_can_see_through(const contentflags_t &contents0, const contentflags_t &contents1) const {
int32_t c0 = contents0.native, c1 = contents1.native;
if (!visible_contents(c0 ^ c1))
return true;
if ((c0 & Q2_CONTENTS_TRANSLUCENT) ||
contents0.is_detail())
c0 = 0;
if ((c1 & Q2_CONTENTS_TRANSLUCENT) ||
contents1.is_detail())
c1 = 0;
// can't see through solid
if ((c0 | c1) & Q2_CONTENTS_SOLID)
return false;
// identical on both sides
if (!(c0 ^ c1))
return true;
return visible_contents(c0 ^ c1);
}
std::string get_contents_display(const contentflags_t &contents) const {
constexpr const char *bitflag_names[] = {
"SOLID",
"WINDOW",
"AUX",
"LAVA",
"SLIME",
"WATER",
"MIST",
"128",
"256",
"512",
"1024",
"2048",
"4096",
"8192",
"16384",
"AREAPORTAL",
"PLAYERCLIP",
"MONSTERCLIP",
"CURRENT_0",
"CURRENT_90",
"CURRENT_180",
"CURRENT_270",
"CURRENT_UP",
"CURRENT_DOWN",
"ORIGIN",
"MONSTER",
"DEADMONSTER",
"DETAIL",
"TRANSLUCENT",
"LADDER",
"1073741824",
"2147483648"
};
std::string s;
for (int32_t i = 0; i < std::size(bitflag_names); i++) {
if (contents.native & (1 << i)) {
if (s.size()) {
s += " | " + std::string(bitflag_names[i]);
} else {
s += bitflag_names[i];
}
}
}
return s;
}
};
@ -331,6 +459,11 @@ bool contentflags_t::is_valid(const gamedef_t *game, bool strict) const {
return game->contents_are_valid(*this, strict);
}
std::string contentflags_t::to_string(const gamedef_t *game) const {
std::string s = game->get_contents_display(*this);
return s;
}
static const char *
BSPVersionString(const bspversion_t *version)
{

View File

@ -344,6 +344,8 @@ struct contentflags_t {
bool types_equal(const contentflags_t &other, const gamedef_t *game) const;
int32_t priority(const gamedef_t *game) const;
std::string to_string(const gamedef_t *game) const;
};
struct bsp29_dnode_t {
@ -1106,6 +1108,8 @@ struct gamedef_t
}
virtual bool contents_are_liquid(const contentflags_t &contents) const = 0;
virtual bool contents_are_valid(const contentflags_t &contents, bool strict = true) const = 0;
virtual bool portal_can_see_through(const contentflags_t &contents0, const contentflags_t &contents1) const = 0;
virtual std::string get_contents_display(const contentflags_t &contents) const = 0;
};
// BSP version struct & instances

View File

@ -214,10 +214,10 @@ struct quark_tx_info_t {
std::optional<extended_texinfo_t> info;
};
int FindMiptex(const char *name, std::optional<extended_texinfo_t> &extended_info);
inline int FindMiptex(const char *name) {
int FindMiptex(const char *name, std::optional<extended_texinfo_t> &extended_info, bool internal = false);
inline int FindMiptex(const char *name, bool internal = false) {
std::optional<extended_texinfo_t> extended_info;
return FindMiptex(name, extended_info);
return FindMiptex(name, extended_info, internal);
}
int FindTexinfo(const mtexinfo_t &texinfo);

View File

@ -170,7 +170,7 @@ typedef struct visfacet_s {
struct visfacet_s *original; // face on node
int outputnumber; // only valid for original faces after
// write surfaces
bool touchesOccupiedLeaf; // internal use in outside.cc
bool touchesOccupiedLeaf; // internal use in outside.cc
vec3_t origin;
vec_t radius;
@ -272,6 +272,7 @@ public:
bool fbspx_brushes;
bool fNoTextures;
const bspversion_t *target_version = &bspver_q1;
const gamedef_t *target_game = target_version->game;
int dxSubdivide;
int dxLeakDist;
int maxNodeSize;

View File

@ -55,7 +55,7 @@ if (embree_FOUND)
endif(embree_FOUND)
add_executable(light ${LIGHT_SOURCES} main.cc)
target_link_libraries (light PRIVATE ${CMAKE_THREAD_LIBS_INIT})
target_link_libraries (light PRIVATE ${CMAKE_THREAD_LIBS_INIT} fmt::fmt)
if (embree_FOUND)
target_link_libraries (light PRIVATE embree)

View File

@ -1,7 +1,7 @@
add_definitions(-DDOUBLEVEC_T)
add_executable(qbsp ${QBSP_SOURCES} main.cc)
target_link_libraries(qbsp ${CMAKE_THREAD_LIBS_INIT} TBB::tbb)
target_link_libraries(qbsp ${CMAKE_THREAD_LIBS_INIT} TBB::tbb fmt::fmt)
install(TARGETS qbsp RUNTIME DESTINATION bin)
# test (copied from light/CMakeLists.txt)
@ -14,4 +14,4 @@ set(QBSP_TEST_SOURCE
add_executable(testqbsp EXCLUDE_FROM_ALL ${QBSP_TEST_SOURCE})
add_test(testqbsp testqbsp)
target_link_libraries (testqbsp ${CMAKE_THREAD_LIBS_INIT} TBB::tbb gtest)
target_link_libraries (testqbsp ${CMAKE_THREAD_LIBS_INIT} TBB::tbb gtest fmt::fmt)

View File

@ -169,13 +169,11 @@ AddToBounds(mapentity_t *entity, const vec3_t point)
//===========================================================================
static int
NormalizePlane(qbsp_plane_t *p)
NormalizePlane(qbsp_plane_t *p, bool flip = true)
{
int i;
vec_t ax, ay, az;
p->outputplanenum = PLANENUM_LEAF;
for (i = 0; i < 3; i++) {
if (p->normal[i] == 1.0) {
p->normal[(i + 1) % 3] = 0;
@ -184,10 +182,12 @@ NormalizePlane(qbsp_plane_t *p)
return 0; /* no flip */
}
if (p->normal[i] == -1.0) {
if (flip) {
p->normal[i] = 1.0;
p->dist = -p->dist;
}
p->normal[(i + 1) % 3] = 0;
p->normal[(i + 2) % 3] = 0;
p->dist = -p->dist;
p->type = PLANE_X + i;
return 1; /* plane flipped */
}
@ -204,7 +204,7 @@ NormalizePlane(qbsp_plane_t *p)
else
p->type = PLANE_ANYZ;
if (p->normal[p->type - PLANE_ANYX] < 0) {
if (flip && p->normal[p->type - PLANE_ANYX] < 0) {
VectorSubtract(vec3_origin, p->normal, p->normal);
p->dist = -p->dist;
return 1; /* plane flipped */
@ -262,7 +262,13 @@ NewPlane(const vec3_t normal, const vec_t dist, int *side)
qbsp_plane_t plane;
VectorCopy(normal, plane.normal);
plane.dist = dist;
*side = NormalizePlane(&plane) ? SIDE_BACK : SIDE_FRONT;
plane.outputplanenum = PLANENUM_LEAF;
int32_t out_side = NormalizePlane(&plane, side != nullptr);
if (side) {
*side = out_side;
}
int index = map.planes.size();
map.planes.push_back(plane);
@ -273,6 +279,7 @@ NewPlane(const vec3_t normal, const vec_t dist, int *side)
/*
* FindPlane
* - Returns a global plane number and the side that will be the front
* - if `side` is null, only an exact match will be fetched.
*/
int
FindPlane(const vec3_t normal, const vec_t dist, int *side)
@ -284,9 +291,11 @@ FindPlane(const vec3_t normal, const vec_t dist, int *side)
for (int i : map.planehash[plane_hash_fn(&plane)]) {
const qbsp_plane_t &p = map.planes.at(i);
if (PlaneEqual(&p, &plane)) {
if (side) {
*side = SIDE_FRONT;
}
return i;
} else if (PlaneInvEqual(&p, &plane)) {
} else if (side && PlaneInvEqual(&p, &plane)) {
*side = SIDE_BACK;
return i;
}
@ -394,11 +403,11 @@ CreateBrushFaces(const mapentity_t *src, hullbrush_t *hullbrush,
hullbrush->maxs[i] = -VECT_MAX;
}
auto DiscardHintSkipFace = (options.target_version->game->id == GAME_QUAKE_II) ? DiscardHintSkipFace_Q2 : DiscardHintSkipFace_Q1;
auto DiscardHintSkipFace = (options.target_game->id == GAME_QUAKE_II) ? DiscardHintSkipFace_Q2 : DiscardHintSkipFace_Q1;
mapface = hullbrush->faces;
for (i = 0; i < hullbrush->numfaces; i++, mapface++) {
if (!hullnum && hullbrush->contents.is_hint()) {
if (hullnum <= 0 && hullbrush->contents.is_hint()) {
/* Don't generate hintskip faces */
const mtexinfo_t &texinfo = map.mtexinfos.at(mapface->texinfo);
@ -499,7 +508,7 @@ CreateBrushFaces(const mapentity_t *src, hullbrush_t *hullbrush,
(rotate_offset[0] != 0.0 || rotate_offset[1] != 0.0 || rotate_offset[2] != 0.0)
&& rottype == rotation_t::hipnotic
&& (hullnum >= 0) // hullnum < 0 corresponds to -wrbrushes clipping hulls
&& options.target_version->game->id != GAME_HEXEN_II; // never do this in Hexen 2
&& options.target_game->id != GAME_HEXEN_II; // never do this in Hexen 2
if (shouldExpand) {
vec_t delta;
@ -849,20 +858,20 @@ Brush_IsDetail(const mapbrush_t *mapbrush)
// contents.
static bool AdjustContentsFromName(const char *texname, contentflags_t &flags) {
if (!Q_strcasecmp(texname, "origin"))
flags = flags.merge(options.target_version->game->create_empty_contents(CFLAGS_ORIGIN));
flags = flags.merge(options.target_game->create_empty_contents(CFLAGS_ORIGIN));
else if (!Q_strcasecmp(texname, "hint"))
flags = flags.merge(options.target_version->game->create_empty_contents(CFLAGS_HINT));
flags = flags.merge(options.target_game->create_empty_contents(CFLAGS_HINT));
else if (!Q_strcasecmp(texname, "clip"))
flags = flags.merge(options.target_version->game->create_solid_contents(CFLAGS_CLIP));
flags = flags.merge(options.target_game->create_solid_contents(CFLAGS_CLIP));
else if (texname[0] == '*') {
if (!Q_strncasecmp(texname + 1, "lava", 4))
flags = flags.merge(options.target_version->game->create_liquid_contents(CONTENTS_LAVA));
flags = flags.merge(options.target_game->create_liquid_contents(CONTENTS_LAVA));
else if (!Q_strncasecmp(texname + 1, "slime", 5))
flags = flags.merge(options.target_version->game->create_liquid_contents(CONTENTS_SLIME));
flags = flags.merge(options.target_version->game->create_liquid_contents(CONTENTS_WATER));
flags = flags.merge(options.target_game->create_liquid_contents(CONTENTS_SLIME));
flags = flags.merge(options.target_game->create_liquid_contents(CONTENTS_WATER));
}
else if (!Q_strncasecmp(texname, "sky", 3))
flags = flags.merge(options.target_version->game->create_sky_contents());
flags = flags.merge(options.target_game->create_sky_contents());
else
return false;
@ -888,7 +897,7 @@ Brush_GetContents_Q1(const mapbrush_t *mapbrush, const contentflags_t &base_cont
}
//and anything else is assumed to be a regular solid.
return options.target_version->game->create_solid_contents();
return options.target_game->create_solid_contents();
}
static contentflags_t
@ -903,6 +912,10 @@ Brush_GetContents_Q2 (const mapbrush_t *mapbrush, const contentflags_t &base_con
const mapface_t &mapface = mapbrush->face(i);
const mtexinfo_t &texinfo = map.mtexinfos.at(mapface.texinfo);
if (texinfo.flags.extended & TEX_EXFLAG_SKIP) {
continue;
}
if (!is_trans && (texinfo.flags.native & (Q2_SURF_TRANS33 | Q2_SURF_TRANS66))) {
is_trans = true;
}
@ -912,7 +925,8 @@ Brush_GetContents_Q2 (const mapbrush_t *mapbrush, const contentflags_t &base_con
}
if (mapface.contents != contents.native) {
logprint("mixed face contents\n"); // TODO: need entity # and brush #
logprint("mixed face contents (%s != %s at line %i)\n", contentflags_t { mapface.contents }.to_string(options.target_game).c_str(),
contents.to_string(options.target_game).c_str(), mapface.linenum); // TODO: need entity # and brush #
break;
}
}
@ -920,7 +934,10 @@ Brush_GetContents_Q2 (const mapbrush_t *mapbrush, const contentflags_t &base_con
// if any side is translucent, mark the contents
// and change solid to window
if (is_trans) {
contents.native = (contents.native & ~Q2_CONTENTS_SOLID) | (Q2_CONTENTS_TRANSLUCENT | Q2_CONTENTS_WINDOW);
contents.native |= Q2_CONTENTS_TRANSLUCENT;
if (contents.native & Q2_CONTENTS_SOLID) {
contents.native = (contents.native & ~Q2_CONTENTS_SOLID) | Q2_CONTENTS_WINDOW;
}
}
// add extended flags that we may need
@ -936,10 +953,25 @@ Brush_GetContents_Q2 (const mapbrush_t *mapbrush, const contentflags_t &base_con
contents.extended |= CFLAGS_ORIGIN;
}
if (contents.native & Q2_CONTENTS_MIST) {
contents.extended |= CFLAGS_DETAIL_ILLUSIONARY;
}
if (is_hint) {
contents.extended |= CFLAGS_HINT;
}
// FIXME: this is a bit of a hack, but this is because clip
// and liquids and stuff are already handled *like* detail by
// the compiler.
if (contents.extended & CFLAGS_DETAIL) {
if (!(contents.native & Q2_CONTENTS_SOLID)) {
contents.extended &= ~CFLAGS_DETAIL;
}
}
Q_assert(contents.is_valid(options.target_game, false));
return contents;
}
@ -985,7 +1017,7 @@ brush_t *LoadBrush(const mapentity_t *src, const mapbrush_t *mapbrush, const con
return NULL;
}
if (options.target_version == &bspver_hl)
if (options.target_game->id == GAME_HALF_LIFE)
{
if (hullnum == 1) {
vec3_t size[2] = { {-16, -16, -36}, {16, 16, 36} };
@ -1006,7 +1038,7 @@ brush_t *LoadBrush(const mapentity_t *src, const mapbrush_t *mapbrush, const con
facelist = CreateBrushFaces(src, &hullbrush, rotate_offset, rottype, hullnum);
}
}
else if (options.target_version->game->id == GAME_HEXEN_II)
else if (options.target_game->id == GAME_HEXEN_II)
{
if (hullnum == 1) {
vec3_t size[2] = { {-16, -16, -32}, {16, 16, 24} };
@ -1200,14 +1232,14 @@ Brush_LoadEntity(mapentity_t *dst, const mapentity_t *src, const int hullnum)
const bool func_illusionary_visblocker =
(0 == Q_strcasecmp(classname, "func_illusionary_visblocker"));
contentflags_t base_contents = options.target_version->game->create_empty_contents();
contentflags_t base_contents = options.target_game->create_empty_contents();
if (func_illusionary_visblocker) {
base_contents.extended |= CFLAGS_ILLUSIONARY_VISBLOCKER;
}
// TODO: move to game
auto Brush_GetContents = (options.target_version->game->id == GAME_QUAKE_II) ? Brush_GetContents_Q2 : Brush_GetContents_Q1;
auto Brush_GetContents = (options.target_game->id == GAME_QUAKE_II) ? Brush_GetContents_Q2 : Brush_GetContents_Q1;
for (int i = 0; i < src->nummapbrushes; i++) {
const mapbrush_t *mapbrush = &src->mapbrush(i);
@ -1314,19 +1346,19 @@ Brush_LoadEntity(mapentity_t *dst, const mapentity_t *src, const int hullnum)
continue;
/* turn solid brushes into detail, if we're in hull0 */
if (hullnum == 0 && contents.is_solid(options.target_version->game)) {
if (hullnum <= 0 && contents.is_solid(options.target_game)) {
if (detail) {
contents.extended |= CFLAGS_DETAIL;
} else if (detail_illusionary) {
contents = contents.merge(options.target_version->game->create_empty_contents(CFLAGS_DETAIL_ILLUSIONARY));
contents = contents.merge(options.target_game->create_empty_contents(CFLAGS_DETAIL_ILLUSIONARY));
} else if (detail_fence) {
contents = contents.merge(options.target_version->game->create_empty_contents(CFLAGS_DETAIL_FENCE)); // fences need to generate leaves
contents = contents.merge(options.target_game->create_empty_contents(CFLAGS_DETAIL_FENCE)); // fences need to generate leaves
}
}
/* func_detail_illusionary don't exist in the collision hull
* (or bspx export) */
if (hullnum && detail_illusionary) {
if ((options.target_game->id != GAME_QUAKE_II && hullnum) && detail_illusionary) {
continue;
}
@ -1349,14 +1381,14 @@ Brush_LoadEntity(mapentity_t *dst, const mapentity_t *src, const int hullnum)
/* "hint" brushes don't affect the collision hulls */
if (contents.is_hint()) {
if (hullnum)
if (hullnum > 0)
continue;
contents = contents.merge(options.target_version->game->create_empty_contents());
contents = contents.merge(options.target_game->create_empty_contents());
}
/* entities never use water merging */
if (dst != pWorldEnt())
contents = contents.merge(options.target_version->game->create_solid_contents());
contents = contents.merge(options.target_game->create_solid_contents());
/* Hack to turn bmodels with "_mirrorinside" into func_detail_fence in hull 0.
this is to allow "_mirrorinside" to work on func_illusionary, func_wall, etc.
@ -1366,19 +1398,19 @@ Brush_LoadEntity(mapentity_t *dst, const mapentity_t *src, const int hullnum)
before writing the bsp, and bmodels normally have CONTENTS_SOLID as their
contents type.
*/
if (dst != pWorldEnt() && hullnum == 0 && (contents.extended & CFLAGS_BMODEL_MIRROR_INSIDE)) {
contents = contents.merge(options.target_version->game->create_empty_contents(CFLAGS_DETAIL_FENCE));
if (dst != pWorldEnt() && hullnum <= 0 && (contents.extended & CFLAGS_BMODEL_MIRROR_INSIDE)) {
contents = contents.merge(options.target_game->create_empty_contents(CFLAGS_DETAIL_FENCE));
}
/* nonsolid brushes don't show up in clipping hulls */
// TODO: will this statement need to be modified since clip
// detail etc aren't native types any more?
if (hullnum > 0 && !contents.is_solid(options.target_version->game) && !contents.is_sky(options.target_version->game))
if (hullnum > 0 && !contents.is_solid(options.target_game) && !contents.is_sky(options.target_game))
continue;
/* sky brushes are solid in the collision hulls */
if (hullnum > 0 && contents.is_sky(options.target_version->game))
contents = contents.merge(options.target_version->game->create_solid_contents());
if (hullnum > 0 && contents.is_sky(options.target_game))
contents = contents.merge(options.target_game->create_solid_contents());
brush_t *brush = LoadBrush(src, mapbrush, contents, rotate_offset, rottype, hullnum);
if (!brush)
@ -1396,10 +1428,10 @@ Brush_LoadEntity(mapentity_t *dst, const mapentity_t *src, const int hullnum)
} else if (brush->contents.is_detail(CFLAGS_DETAIL_FENCE)) {
brush->next = dst->detail_fence;
dst->detail_fence = brush;
} else if (brush->contents.is_solid(options.target_version->game)) {
} else if (brush->contents.is_solid(options.target_game)) {
brush->next = dst->solid;
dst->solid = brush;
} else if (brush->contents.is_sky(options.target_version->game)) {
} else if (brush->contents.is_sky(options.target_game)) {
brush->next = dst->sky;
dst->sky = brush;
} else {

View File

@ -55,9 +55,8 @@ MakeSkipTexinfo()
mtexinfo_t mt { };
mt.miptex = FindMiptex("skip");
mt.miptex = FindMiptex("skip", true);
mt.flags = { 0, TEX_EXFLAG_SKIP };
memset(&mt.vecs, 0, sizeof(mt.vecs));
return FindTexinfo(mt);
}
@ -374,7 +373,7 @@ SaveFacesToPlaneList(face_t *facelist, bool mirror, std::map<int, face_t *> &pla
// to force the right content type for the leaf, but we don't actually
// want the face. So just set the texinfo to "skip" so it gets deleted.
if ((face->contents[1].is_detail() || (face->contents[1].extended & CFLAGS_WAS_ILLUSIONARY))
|| (options.fContentHack && face->contents[1].is_structural_solid(options.target_version->game))) {
|| (options.fContentHack && face->contents[1].is_structural_solid(options.target_game))) {
// if CFLAGS_BMODEL_MIRROR_INSIDE is set, never change to skip
if (!(face->contents[1].extended & CFLAGS_BMODEL_MIRROR_INSIDE)) {
@ -423,7 +422,7 @@ contents override the face inside contents.
static void
SaveInsideFaces(face_t *face, const brush_t *clipbrush, face_t **savelist)
{
Q_assert(!clipbrush->contents.is_structural_solid(options.target_version->game));
Q_assert(!clipbrush->contents.is_structural_solid(options.target_game));
face_t *next;
@ -434,7 +433,7 @@ SaveInsideFaces(face_t *face, const brush_t *clipbrush, face_t **savelist)
next = face->next;
face->contents[0] = clipbrush->contents;
if (face->contents[1].is_structural_sky_or_solid(options.target_version->game)
if (face->contents[1].is_structural_sky_or_solid(options.target_game)
&& clipbrush->contents.is_detail(CFLAGS_DETAIL)) {
// This case is when a structural and detail brush are touching,
// and we want to save the sturctural face that is
@ -450,14 +449,14 @@ SaveInsideFaces(face_t *face, const brush_t *clipbrush, face_t **savelist)
// marked as empty here, and the detail faces have their "back"
// marked as detail.
face->contents[0] = options.target_version->game->create_empty_contents(CFLAGS_STRUCTURAL_COVERED_BY_DETAIL);
face->contents[0] = options.target_game->create_empty_contents(CFLAGS_STRUCTURAL_COVERED_BY_DETAIL);
face->texinfo = MakeSkipTexinfo();
}
// N.B.: We don't need a hack like above for when clipbrush->contents == CONTENTS_DETAIL_ILLUSIONARY.
// These would create leaks
Q_assert(!(face->contents[1].is_structural_sky_or_solid(options.target_version->game) &&
Q_assert(!(face->contents[1].is_structural_sky_or_solid(options.target_game) &&
face->contents[0].is_detail(CFLAGS_DETAIL)));
/*
@ -467,7 +466,7 @@ SaveInsideFaces(face_t *face, const brush_t *clipbrush, face_t **savelist)
if (face->contents[1].is_detail(CFLAGS_DETAIL_ILLUSIONARY)) {
face->contents[1] = { clipbrush->contents.native, (face->contents[1].extended & ~CFLAGS_DETAIL_ILLUSIONARY) | CFLAGS_WAS_ILLUSIONARY };
}
if (face->contents[1].is_empty(options.target_version->game)) {
if (face->contents[1].is_empty(options.target_game)) {
face->contents[1] = clipbrush->contents;
}
@ -533,7 +532,7 @@ CopyBrushFaces(const brush_t *brush)
brushfaces++;
newface = (face_t *)AllocMem(OTHER, sizeof(face_t), true);
*newface = *face;
newface->contents[0] = options.target_version->game->create_empty_contents();
newface->contents[0] = options.target_game->create_empty_contents();
newface->contents[1] = brush->contents;
newface->lmshift[0] = brush->lmshift;
newface->lmshift[1] = brush->lmshift;
@ -626,7 +625,7 @@ CSGFaces(const mapentity_t *entity)
// TODO: this might break because this == won't catch the extended types now.
// might need a specific function for this one.
if (clipbrush->contents.types_equal(brush->contents, options.target_version->game)
if (clipbrush->contents.types_equal(brush->contents, options.target_game)
&& !clipbrush->contents.clips_same_type()) {
/* _noclipfaces key */
continue;
@ -668,18 +667,18 @@ CSGFaces(const mapentity_t *entity)
*
* FIXME: clean this up, the predicate seems to be "can you see 'brush' from inside 'clipbrush'"
*/
if ((brush->contents.is_structural_solid(options.target_version->game) && !clipbrush->contents.is_structural_solid(options.target_version->game))
if ((brush->contents.is_structural_solid(options.target_game) && !clipbrush->contents.is_structural_solid(options.target_game))
|| (brush->contents.is_structural_sky(options.target_version->game) && !clipbrush->contents.is_structural_sky_or_solid(options.target_version->game))
|| (brush->contents.is_structural_sky(options.target_game) && !clipbrush->contents.is_structural_sky_or_solid(options.target_game))
|| ((brush->contents.is_solid(options.target_version->game) && brush->contents.is_detail(CFLAGS_DETAIL)) &&
(!clipbrush->contents.is_solid(options.target_version->game)
&& !clipbrush->contents.is_sky(options.target_version->game)
|| ((brush->contents.is_solid(options.target_game) && brush->contents.is_detail(CFLAGS_DETAIL)) &&
(!clipbrush->contents.is_solid(options.target_game)
&& !clipbrush->contents.is_sky(options.target_game)
&& !clipbrush->contents.is_detail(CFLAGS_DETAIL)))
|| (brush->contents.is_liquid(options.target_version->game) && clipbrush->contents.is_detail(CFLAGS_DETAIL_ILLUSIONARY))
|| (brush->contents.is_liquid(options.target_game) && clipbrush->contents.is_detail(CFLAGS_DETAIL_ILLUSIONARY))
|| (brush->contents.is_fence() && (clipbrush->contents.is_liquid(options.target_version->game) || clipbrush->contents.is_fence())))
|| (brush->contents.is_fence() && (clipbrush->contents.is_liquid(options.target_game) || clipbrush->contents.is_fence())))
{
SaveInsideFaces(inside, clipbrush, &outside);
} else {
@ -701,7 +700,7 @@ CSGFaces(const mapentity_t *entity)
* All of the faces left on the outside list are real surface faces
* If the brush is non-solid, mirror faces for the inside view
*/
const bool mirror = options.fContentHack ? true : !brush->contents.is_solid(options.target_version->game);
const bool mirror = options.fContentHack ? true : !brush->contents.is_solid(options.target_game);
SaveFacesToPlaneList(outside, mirror, planefaces);
}
surface_t *surfaces = BuildSurfaces(planefaces);

View File

@ -195,19 +195,17 @@ static std::optional<wal_t> LoadWal(const char *name) {
}
int
FindMiptex(const char *name, std::optional<extended_texinfo_t> &extended_info)
FindMiptex(const char *name, std::optional<extended_texinfo_t> &extended_info, bool internal)
{
const char *pathsep;
int i;
if (options.target_version->game->id != GAME_QUAKE_II) {
if (options.target_game->id != GAME_QUAKE_II) {
/* Ignore leading path in texture names (Q2 map compatibility) */
pathsep = strrchr(name, '/');
if (pathsep)
name = pathsep + 1;
extended_info = extended_texinfo_t { };
for (i = 0; i < map.nummiptex(); i++) {
const texdata_t &tex = map.miptex.at(i);
@ -226,18 +224,22 @@ FindMiptex(const char *name, std::optional<extended_texinfo_t> &extended_info)
}
} else {
// load .wal first
std::optional<wal_t> wal = LoadWal(name);
std::optional<wal_t> wal;
// FIXME: this spams the console if wal not found, because we
// need to load the wal to figure out if it will match anything
// in the list...
if (!wal.has_value()) {
Message(msgLiteral, "Couldn't locate wal for %s\n", name);
wal = wal_t {};
if (!internal || !extended_info.has_value()) {
wal = LoadWal(name);
if (!wal.has_value()) {
//FIXME
//Message(msgLiteral, "Couldn't locate wal for %s\n", name);
if (!extended_info.has_value()) {
extended_info = extended_texinfo_t { };
}
} else if (!extended_info.has_value()) {
extended_info = extended_texinfo_t { wal->contents, wal->flags, wal->value };
}
}
extended_info = extended_info.value_or(extended_texinfo_t { wal->contents, wal->flags, wal->value });
for (i = 0; i < map.nummiptex(); i++) {
const texdata_t &tex = map.miptex.at(i);
@ -250,10 +252,10 @@ FindMiptex(const char *name, std::optional<extended_texinfo_t> &extended_info)
}
i = map.miptex.size();
map.miptex.push_back({ name, wal->flags, wal->value });
map.miptex.push_back({ name, extended_info->flags, extended_info->value });
/* Handle animating textures carefully */
if (wal->anim_name[0]) {
if (wal && wal->anim_name[0]) {
FindMiptex(wal->anim_name);
}
}
@ -365,7 +367,13 @@ SurfFlagsForEntity(const mtexinfo_t &texinfo, const mapentity_t *entity)
const char *texname = map.miptex.at(texinfo.miptex).name.c_str();
const int shadow = atoi(ValueForKey(entity, "_shadow"));
// These flags are pulled from surf flags in Q2.
if (options.target_version->game->id != GAME_QUAKE_II) {
// TODO: the Q1 version of this block can now be moved into texinfo
// loading by shoving them inside of texinfo.flags like
// Q2 does. Similarly, we can move the Q2 block out
// into a special function, like.. I dunno,
// game->surface_flags_from_name(surfflags_t &inout, const char *name)
// which we can just call instead of this block.
if (options.target_game->id != GAME_QUAKE_II) {
if (IsSkipName(texname))
flags.extended |= TEX_EXFLAG_SKIP;
if (IsHintName(texname))
@ -375,9 +383,15 @@ SurfFlagsForEntity(const mtexinfo_t &texinfo, const mapentity_t *entity)
} else {
flags.native = texinfo.flags.native;
if (texinfo.flags.native & Q2_SURF_NODRAW)
// This fixes a bug in some old maps.
if ((flags.native & (Q2_SURF_SKY | Q2_SURF_NODRAW)) == (Q2_SURF_SKY | Q2_SURF_NODRAW)) {
flags.native &= ~Q2_SURF_NODRAW;
//logprint("Corrected invalid SKY flag\n");
}
if ((flags.native & Q2_SURF_NODRAW) || IsSkipName(texname))
flags.extended |= TEX_EXFLAG_SKIP;
if (texinfo.flags.native & Q2_SURF_HINT)
if ((flags.native & Q2_SURF_HINT) || IsHintName(texname))
flags.extended |= TEX_EXFLAG_HINT;
}
if (IsNoExpandName(texname))
@ -488,7 +502,7 @@ ParseEpair(parser_t *parser, mapentity_t *entity)
// Quake II uses multiple starts for level transitions/backtracking.
// TODO: instead, this should check targetnames. There should only be
// one info_player_start per targetname in Q2.
if (options.target_version->game->id != GAME_QUAKE_II && (rgfStartSpots & info_player_start))
if (options.target_game->id != GAME_QUAKE_II && (rgfStartSpots & info_player_start))
Message(msgWarning, warnMultipleStarts);
rgfStartSpots |= info_player_start;
} else if (!Q_strcasecmp(epair->value, "info_player_deathmatch")) {
@ -1485,6 +1499,8 @@ ParseTextureDef(parser_t *parser, mapface_t &mapface, const mapbrush_t *brush, m
tx->flags = mapface.flags = { extinfo.info->flags };
tx->value = mapface.value = extinfo.info->value;
Q_assert(contentflags_t { mapface.contents }.is_valid(options.target_game, false));
if (!planepts || !plane)
return;
@ -1678,11 +1694,12 @@ ParseBrush(parser_t *parser, const mapentity_t *entity)
if (face.get() == nullptr)
continue;
if (options.target_version->game->id == GAME_QUAKE_II) {
// FIXME: can we move this somewhere later?
if (options.target_game->id == GAME_QUAKE_II) {
// translucent objects are automatically classified as detail
//if ((face->flags.native & (Q2_SURF_TRANS33 | Q2_SURF_TRANS66))
// || (face->contents & (Q2_CONTENTS_PLAYERCLIP | Q2_CONTENTS_MONSTERCLIP)))
// face->contents |= Q2_CONTENTS_DETAIL;
if ((face->flags.native & (Q2_SURF_TRANS33 | Q2_SURF_TRANS66))
|| (face->contents & (Q2_CONTENTS_PLAYERCLIP | Q2_CONTENTS_MONSTERCLIP)))
face->contents |= Q2_CONTENTS_DETAIL;
if (!(face->contents & (((Q2_LAST_VISIBLE_CONTENTS << 1)-1)
| Q2_CONTENTS_PLAYERCLIP | Q2_CONTENTS_MONSTERCLIP) ) )

View File

@ -338,7 +338,7 @@ ClearOutFaces(node_t *node)
}
// visit the leaf
if (!node->contents.is_solid(options.target_version->game)) {
if (!node->contents.is_solid(options.target_game)) {
return;
}
@ -365,8 +365,8 @@ OutLeafsToSolid_r(node_t *node, int *outleafs_count)
return;
// Don't fill sky, or count solids as outleafs
if (node->contents.is_sky(options.target_version->game)
|| node->contents.is_solid(options.target_version->game))
if (node->contents.is_sky(options.target_game)
|| node->contents.is_solid(options.target_game))
return;
// Now check all faces touching the leaf. If any of them are partially going into the occupied part of the map,
@ -383,7 +383,7 @@ OutLeafsToSolid_r(node_t *node, int *outleafs_count)
}
// Finally, we can fill it in as void.
node->contents = options.target_version->game->create_solid_contents();
node->contents = options.target_game->create_solid_contents();
*outleafs_count += 1;
}

View File

@ -59,47 +59,42 @@ ClusterContents(const node_t *node)
if (node->planenum == PLANENUM_LEAF)
return node->contents;
return options.target_version->game->cluster_contents(ClusterContents(node->children[0]), ClusterContents(node->children[1]));
return options.target_game->cluster_contents(ClusterContents(node->children[0]), ClusterContents(node->children[1]));
}
/*
* Return true if possible to see the through the contents of the portals nodes
*/
/* Return true if possible to see the through the contents of the portals nodes */
static bool
PortalThru(const portal_t *p)
{
contentflags_t contents0 = ClusterContents(p->nodes[0]);
contentflags_t contents1 = ClusterContents(p->nodes[1]);
/* Can't see through solids */
if (contents0.is_structural_solid(options.target_version->game) || contents1.is_structural_solid(options.target_version->game))
return false;
/* Can't see through func_illusionary_visblocker */
if ((contents0.extended | contents1.extended) & CFLAGS_ILLUSIONARY_VISBLOCKER)
return false;
/* If contents values are the same and not solid, can see through */
if (contents0 == contents1)
return true;
// FIXME: we can't move this directly to portal_can_see_through because
// "options" isn't exposed there.
if (options.target_game->id != GAME_QUAKE_II) {
/* If water is transparent, liquids are like empty space */
if (options.fTranswater) {
if (contents0.is_liquid(options.target_game) && contents1.is_empty(options.target_game))
return true;
if (contents1.is_liquid(options.target_game) && contents0.is_empty(options.target_game))
return true;
}
/* If water is transparent, liquids are like empty space */
if (options.fTranswater) {
if (contents0.is_liquid(options.target_version->game) && contents1.is_empty(options.target_version->game))
return true;
if (contents1.is_liquid(options.target_version->game) && contents0.is_empty(options.target_version->game))
return true;
/* If sky is transparent, then sky is like empty space */
if (options.fTranssky) {
if (contents0.is_sky(options.target_game) && contents1.is_empty(options.target_game))
return true;
if (contents0.is_empty(options.target_game) && contents1.is_sky(options.target_game))
return true;
}
}
/* If sky is transparent, then sky is like empty space */
if (options.fTranssky) {
if (contents0.is_sky(options.target_version->game) && contents1.is_empty(options.target_version->game))
return true;
if (contents0.is_empty(options.target_version->game) && contents1.is_sky(options.target_version->game))
return true;
}
return false;
// Check per-game visibility
return options.target_game->portal_can_see_through(contents0, contents1);
}
static void
@ -116,7 +111,7 @@ WritePortals_r(node_t *node, FILE *portalFile, bool clusters)
WritePortals_r(node->children[1], portalFile, clusters);
return;
}
if (node->contents.is_solid(options.target_version->game))
if (node->contents.is_solid(options.target_game))
return;
for (p = node->portals; p; p = next) {
@ -161,7 +156,7 @@ WriteClusters_r(node_t *node, FILE *portalFile, int viscluster)
viscluster = WriteClusters_r(node->children[1], portalFile, viscluster);
return viscluster;
}
if (node->contents.is_solid(options.target_version->game))
if (node->contents.is_solid(options.target_game))
return viscluster;
/* If we're in the next cluster, start a new line */
@ -223,7 +218,7 @@ NumberLeafs_r(node_t *node, portal_state_t *state, int cluster)
return;
}
if (node->contents.is_solid(options.target_version->game)) {
if (node->contents.is_structural_solid(options.target_game)) {
/* solid block, viewpoint never inside */
node->visleafnum = -1;
node->viscluster = -1;
@ -380,7 +375,7 @@ MakeHeadnodePortals(const mapentity_t *entity, node_t *node)
}
outside_node.planenum = PLANENUM_LEAF;
outside_node.contents = options.target_version->game->create_solid_contents();
outside_node.contents = options.target_game->create_solid_contents();
outside_node.portals = NULL;
for (i = 0; i < 3; i++)
@ -671,7 +666,7 @@ PortalizeWorld(const mapentity_t *entity, node_t *headnode, const int hullnum)
MakeHeadnodePortals(entity, headnode);
CutNodePortals_r(headnode, &state);
if (!hullnum) {
if (hullnum <= 0) {
/* save portal file for vis tracing */
WritePortalfile(headnode, &state);

View File

@ -20,7 +20,8 @@
*/
#include <memory>
#include <string.h>
#include <cstring>
#include <algorithm>
#include <common/log.hh>
#include <common/aabb.hh>
@ -36,27 +37,38 @@ static const char *IntroString =
options_t options;
bool node_t::opaque() const {
return contents.is_structural_sky_or_solid(options.target_version->game);
return contents.is_structural_sky_or_solid(options.target_game);
}
static struct {
uint32_t total_brushes, total_brush_sides;
uint32_t total_leaf_brushes, unique_leaf_brushes;
} brush_state;
static void ExportBrushList_r(const mapentity_t *entity, node_t *node)
{
if (node->planenum == PLANENUM_LEAF)
{
if (node->visleafnum == -1) {
node->firstleafbrush = map.exported_leafbrushes.size();
node->numleafbrushes = 0;
if (node->contents.native) {
int32_t b_id = 0;
std::vector<uint32_t> brushes;
for (const brush_t *b = entity->brushes; b; b = b->next, b_id++)
{
if (aabb3f(qvec3f(node->mins[0], node->mins[1], node->mins[2]), qvec3f(node->maxs[0], node->maxs[1], node->maxs[2])).intersectWith(
aabb3f(qvec3f(b->mins[0], b->mins[1], b->mins[2]), qvec3f(b->maxs[0], b->maxs[1], b->maxs[2]))).valid) {
map.exported_leafbrushes.push_back(b_id);
node->numleafbrushes++;
brushes.push_back(b_id);
}
}
node->numleafbrushes = brushes.size();
brush_state.total_leaf_brushes += node->numleafbrushes;
if (brushes.size()) {
node->firstleafbrush = map.exported_leafbrushes.size();
map.exported_leafbrushes.insert(map.exported_leafbrushes.end(), brushes.begin(), brushes.end());
brush_state.unique_leaf_brushes += node->numleafbrushes;
}
}
return;
@ -68,23 +80,75 @@ static void ExportBrushList_r(const mapentity_t *entity, node_t *node)
static void ExportBrushList(const mapentity_t *entity, node_t *node)
{
brush_state = { };
for (const brush_t *b = entity->brushes; b; b = b->next)
{
dbrush_t brush { (int32_t) map.exported_brushsides.size(), 0, b->contents.native };
for (const face_t *f = b->faces; f; f = f->next)
{
if (map.planes[f->planenum].outputplanenum == -1) {
continue;
int32_t planenum = f->planenum;
int32_t outputplanenum;
if (f->planeside) {
vec3_t flipped;
VectorCopy(map.planes[f->planenum].normal, flipped);
VectorInverse(flipped);
planenum = FindPlane(flipped, -map.planes[f->planenum].dist, nullptr);
outputplanenum = ExportMapPlane(planenum);
} else {
planenum = FindPlane(map.planes[f->planenum].normal, map.planes[f->planenum].dist, nullptr);
outputplanenum = ExportMapPlane(planenum);
}
map.exported_brushsides.push_back({ (uint32_t) map.planes[f->planenum].outputplanenum, map.mtexinfos[f->texinfo].outputnum.value_or(-1) });
map.exported_brushsides.push_back({ (uint32_t) outputplanenum, map.mtexinfos[f->texinfo].outputnum.value_or(-1) });
brush.numsides++;
brush_state.total_brush_sides++;
}
// add any axis planes not contained in the brush to bevel off corners
for (int32_t x=0 ; x<3 ; x++)
for (int32_t s=-1 ; s<=1 ; s+=2)
{
// add the plane
vec3_t normal { };
float dist;
VectorCopy (vec3_origin, normal);
normal[x] = s;
if (s == -1)
dist = -b->mins[x];
else
dist = b->maxs[x];
int32_t side;
int32_t planenum = FindPlane(normal, dist, &side);
face_t *f;
for (f = b->faces; f; f = f->next)
if (f->planenum == planenum)
break;
if (f == nullptr)
{
planenum = FindPlane(normal, dist, nullptr);
int32_t outputplanenum = ExportMapPlane(planenum);
map.exported_brushsides.push_back({ (uint32_t) outputplanenum, map.exported_brushsides[map.exported_brushsides.size() - 1].texinfo });
brush.numsides++;
brush_state.total_brush_sides++;
}
}
map.exported_brushes.push_back(brush);
brush_state.total_brushes++;
}
ExportBrushList_r(entity, node);
Message(msgStat, "%8u total brushes", brush_state.total_brushes);
Message(msgStat, "%8u total brush sides", brush_state.total_brush_sides);
Message(msgStat, "%8u total leaf brushes", brush_state.total_leaf_brushes);
Message(msgStat, "%8u unique leaf brushes (%.2f%%)", brush_state.unique_leaf_brushes, (brush_state.unique_leaf_brushes / (float) brush_state.total_leaf_brushes) * 100);
}
/*
@ -126,7 +190,7 @@ ProcessEntity(mapentity_t *entity, const int hullnum)
if (options.fVerbose)
PrintEntity(entity);
if (hullnum == 0)
if (hullnum <= 0)
Message(msgStat, "MODEL: %s", mod);
SetKeyValue(entity, "model", mod);
}
@ -218,11 +282,11 @@ ProcessEntity(mapentity_t *entity, const int hullnum)
*/
surfs = CSGFaces(entity);
if (options.fObjExport && entity == pWorldEnt() && hullnum == 0) {
if (options.fObjExport && entity == pWorldEnt() && hullnum <= 0) {
ExportObj_Surfaces("post_csg", surfs);
}
if (hullnum != 0) {
if (hullnum > 0) {
nodes = SolidBSP(entity, surfs, true);
if (entity == pWorldEnt() && !options.fNofill) {
// assume non-world bmodels are simple
@ -297,11 +361,13 @@ ProcessEntity(mapentity_t *entity, const int hullnum)
}
firstface = MakeFaceEdges(entity, nodes);
ExportDrawNodes(entity, nodes, firstface);
if (options.target_version->game->id == GAME_QUAKE_II) {
if (options.target_game->id == GAME_QUAKE_II) {
Message(msgProgress, "Calculating Brush List");
ExportBrushList(entity, nodes);
}
ExportDrawNodes(entity, nodes, firstface);
}
FreeBrushes(entity);
@ -624,6 +690,12 @@ CreateHulls(void)
if (!options.fNoverbose)
options.fVerbose = true;
if (options.target_game->id == GAME_QUAKE_II)
{
CreateSingleHull(-1);
return;
}
CreateSingleHull(0);
/* ignore the clipping hulls altogether */
@ -633,9 +705,10 @@ CreateHulls(void)
CreateSingleHull(1);
CreateSingleHull(2);
if (options.target_version == &bspver_hl)
// FIXME: use game->get_hull_count
if (options.target_game->id == GAME_HALF_LIFE)
CreateSingleHull(3);
else if (options.target_version->game->id == GAME_HEXEN_II)
else if (options.target_game->id == GAME_HEXEN_II)
{ /*note: h2mp doesn't use hull 2 automatically, however gamecode can explicitly set ent.hull=3 to access it*/
CreateSingleHull(3);
CreateSingleHull(4);
@ -1033,9 +1106,12 @@ ParseOptions(char *szOptions)
}
// force specific flags for Q2
if (options.target_version->game->id == GAME_QUAKE_II) {
if (options.target_game->id == GAME_QUAKE_II) {
options.fNoclip = true;
}
// update target game
options.target_game = options.target_version->game;
}

View File

@ -74,10 +74,10 @@ DetailToSolid(node_t *node)
// If both children are solid, we can merge the two leafs into one.
// DarkPlaces has an assertion that fails if both children are
// solid.
if (node->children[0]->contents.is_structural_solid(options.target_version->game)
&& node->children[1]->contents.is_structural_solid(options.target_version->game)) {
if (node->children[0]->contents.is_structural_solid(options.target_game)
&& node->children[1]->contents.is_structural_solid(options.target_game)) {
// This discards any faces on-node. Should be safe (?)
ConvertNodeToLeaf(node, options.target_version->game->create_solid_contents());
ConvertNodeToLeaf(node, options.target_game->create_solid_contents());
}
}
}
@ -538,8 +538,9 @@ CalcSurfaceInfo(surface_t *surf)
surf->has_struct = false;
for (const face_t *f = surf->faces; f; f = f->next) {
if (!f->contents[0].is_valid(options.target_version->game, false) || !f->contents[1].is_valid(options.target_version->game, false))
Error("Bad contents in face (%s)", __func__);
for (auto &contents : f->contents)
if (!contents.is_valid(options.target_game, false))
Error("Bad contents in face: %s (%s)", contents.to_string(options.target_game).c_str(), __func__);
surf->lmshift = (f->lmshift[0]<f->lmshift[1])?f->lmshift[0]:f->lmshift[1];
@ -751,19 +752,19 @@ LinkConvexFaces(surface_t *planelist, node_t *leafnode)
for (face_t *f = surf->faces; f; f = f->next) {
count++;
const int currentpri = contents.has_value() ? contents->priority(options.target_version->game) : -1;
const int fpri = f->contents[0].priority(options.target_version->game);
const int currentpri = contents.has_value() ? contents->priority(options.target_game) : -1;
const int fpri = f->contents[0].priority(options.target_game);
if (fpri > currentpri) {
contents = f->contents[0];
}
// HACK: Handle structural covered by detail.
if (f->contents[0].extended & CFLAGS_STRUCTURAL_COVERED_BY_DETAIL) {
Q_assert(f->contents[0].is_empty(options.target_version->game));
Q_assert(f->contents[0].is_empty(options.target_game));
const contentflags_t solid_detail = options.target_version->game->create_solid_contents(CFLAGS_DETAIL);
const contentflags_t solid_detail = options.target_game->create_solid_contents(CFLAGS_DETAIL);
if (solid_detail.priority(options.target_version->game) > currentpri) {
if (solid_detail.priority(options.target_game) > currentpri) {
contents = solid_detail;
}
}
@ -773,7 +774,7 @@ LinkConvexFaces(surface_t *planelist, node_t *leafnode)
// NOTE: This is crazy..
// Liquid leafs get assigned liquid content types because of the
// "cosmetic" mirrored faces.
leafnode->contents = contents.value_or(options.target_version->game->create_solid_contents()); // FIXME: Need to create CONTENTS_DETAIL sometimes?
leafnode->contents = contents.value_or(options.target_game->create_solid_contents()); // FIXME: Need to create CONTENTS_DETAIL sometimes?
if (leafnode->contents.extended & CFLAGS_ILLUSIONARY_VISBLOCKER) {
c_illusionary_visblocker++;
@ -783,14 +784,14 @@ LinkConvexFaces(surface_t *planelist, node_t *leafnode)
c_detail_illusionary++;
} else if (leafnode->contents.extended & CFLAGS_DETAIL) {
c_detail++;
} else if (leafnode->contents.is_empty(options.target_version->game)) {
} else if (leafnode->contents.is_empty(options.target_game)) {
c_empty++;
} else if (leafnode->contents.is_solid(options.target_version->game)) {
} else if (leafnode->contents.is_solid(options.target_game)) {
c_solid++;
} else if (leafnode->contents.is_liquid(options.target_version->game) || leafnode->contents.is_sky(options.target_version->game)) {
} else if (leafnode->contents.is_liquid(options.target_game) || leafnode->contents.is_sky(options.target_game)) {
c_water++;
} else {
Error("Bad contents in face (%s)", __func__);
//Error("Bad contents in face: %s (%s)", leafnode->contents.to_string(options.target_game).c_str(), __func__);
}
// write the list of the original faces to the leaf's markfaces
@ -949,11 +950,11 @@ SolidBSP(const mapentity_t *entity, surface_t *surfhead, bool midsplit)
}
headnode->children[0] = (node_t *)AllocMem(OTHER, sizeof(node_t), true);
headnode->children[0]->planenum = PLANENUM_LEAF;
headnode->children[0]->contents = options.target_version->game->create_empty_contents();
headnode->children[0]->contents = options.target_game->create_empty_contents();
headnode->children[0]->markfaces = (face_t **)AllocMem(OTHER, sizeof(face_t *), true);
headnode->children[1] = (node_t *)AllocMem(OTHER, sizeof(node_t), true);
headnode->children[1]->planenum = PLANENUM_LEAF;
headnode->children[1]->contents = options.target_version->game->create_empty_contents();
headnode->children[1]->contents = options.target_game->create_empty_contents();
headnode->children[1]->markfaces = (face_t **)AllocMem(OTHER, sizeof(face_t *), true);
return headnode;

View File

@ -36,7 +36,7 @@ SubdivideFace(face_t *f, face_t **prevptr)
{
vec_t mins, maxs;
vec_t v;
int axis, i;
int axis;
qbsp_plane_t plane;
face_t *front, *back, *next;
const mtexinfo_t *tex;
@ -49,7 +49,7 @@ SubdivideFace(face_t *f, face_t **prevptr)
tex = &map.mtexinfos.at(f->texinfo);
if (tex->flags.extended & (TEX_EXFLAG_SKIP | TEX_EXFLAG_HINT) ||
!options.target_version->game->surf_is_subdivided(tex->flags))
!options.target_game->surf_is_subdivided(tex->flags))
return;
//subdivision is pretty much pointless other than because of lightmap block limits
//one lightmap block will always be added at the end, for smooth interpolation
@ -292,7 +292,7 @@ GetEdge(mapentity_t *entity, const vec3_t p1, const vec3_t p2,
int v1, v2;
int i;
if (!face->contents[0].is_valid(options.target_version->game, false))
if (!face->contents[0].is_valid(options.target_game, false))
Error("Face with invalid contents (%s)", __func__);
v1 = GetVertex(entity, p1);

View File

@ -357,7 +357,7 @@ FixFaceEdges
static void
FixFaceEdges(face_t *face, face_t *superface, face_t **facelist)
{
int i, j, k;
int i, j;
wedge_t *edge;
wvert_t *v;
vec_t t1, t2;

View File

@ -77,7 +77,7 @@ WAD_LoadInfo(wad_t &wad, bool external)
int w = LittleLong(miptex.width);
int h = LittleLong(miptex.height);
lump.size = sizeof(miptex) + (w>>0)*(h>>0) + (w>>1)*(h>>1) + (w>>2)*(h>>2) + (w>>3)*(h>>3);
if (options.target_version == &bspver_hl)
if (options.target_game->id == GAME_HALF_LIFE)
lump.size += 2+3*256; //palette size+palette data
lump.size = (lump.size+3) & ~3; //keep things aligned if we can.
@ -219,7 +219,7 @@ WAD_LoadLump(const wad_t &wad, const char *name, uint8_t *dest)
memcpy(dest+out->offsets[2], data.data()+(in->offsets[2]), (in->width>>2)*(in->height>>2));
memcpy(dest+out->offsets[3], data.data()+(in->offsets[3]), (in->width>>3)*(in->height>>3));
if (options.target_version == &bspver_hl)
if (options.target_game->id == GAME_HALF_LIFE)
{ //palette size. 256 in little endian.
dest[palofs+0] = ((256>>0)&0xff);
dest[palofs+1] = ((256>>8)&0xff);
@ -311,7 +311,7 @@ WADList_Process()
}
// Q2 doesn't use texdata
if (options.target_version->game->id == GAME_QUAKE_II) {
if (options.target_game->id == GAME_QUAKE_II) {
return;
}

View File

@ -28,23 +28,10 @@
#include <cstdint>
static void
AssertVanillaContentType(int content)
AssertVanillaContentType(const contentflags_t &flags)
{
// TODO
if (options.target_version->game->id == GAME_QUAKE_II) {
return;
}
switch (content) {
case CONTENTS_EMPTY:
case CONTENTS_SOLID:
case CONTENTS_WATER:
case CONTENTS_SLIME:
case CONTENTS_LAVA:
case CONTENTS_SKY:
break;
default:
Error("Internal error: Tried to save compiler-internal contents type %s\n", GetContentsName({ content }));
if (!flags.is_valid(options.target_game, false)) {
Error("Internal error: Tried to save invalid contents type %s\n", GetContentsName(flags));
}
}
@ -58,7 +45,7 @@ RemapContentsForExport(const contentflags_t &content)
*
* Normally solid leafs are not written and just referenced as leaf 0.
*/
return options.target_version->game->create_solid_contents();
return options.target_game->create_solid_contents();
}
return content;
@ -195,8 +182,9 @@ ExportLeaf(mapentity_t *entity, node_t *node)
map.exported_leafs.push_back({});
mleaf_t *dleaf = &map.exported_leafs.back();
dleaf->contents = RemapContentsForExport(node->contents).native;
AssertVanillaContentType(dleaf->contents);
const contentflags_t remapped = RemapContentsForExport(node->contents);
AssertVanillaContentType(remapped);
dleaf->contents = remapped.native;
/*
* write bounding box info
@ -263,7 +251,7 @@ ExportDrawNodes(mapentity_t *entity, node_t *node)
if (node->children[i]->planenum == PLANENUM_LEAF) {
// In Q2, all leaves must have their own ID even if they share solidity.
// (probably for collision purposes? makes sense given they store leafbrushes)
if (options.target_version->game->id != GAME_QUAKE_II && node->children[i]->contents.is_solid(options.target_version->game))
if (options.target_game->id != GAME_QUAKE_II && node->children[i]->contents.is_solid(options.target_game))
dnode->children[i] = -1;
else {
int nextLeafIndex = static_cast<int>(map.exported_leafs.size());
@ -340,7 +328,7 @@ BeginBSPFile(void)
// Leave room for leaf 0 (must be solid)
map.exported_leafs.push_back({});
map.exported_leafs.back().contents = options.target_version->game->create_solid_contents().native;
map.exported_leafs.back().contents = options.target_game->create_solid_contents().native;
Q_assert(map.exported_leafs.size() == 1);
}
@ -386,10 +374,10 @@ WriteExtendedTexinfoFlags(void)
int count = 0;
for (const auto &tx : texinfos_sorted) {
if (tx.outputnum.has_value())
if (!tx.outputnum.has_value())
continue;
Q_assert(count == tx.outputnum); // check we are outputting them in the proper sequence
Q_assert(count == tx.outputnum.value()); // check we are outputting them in the proper sequence
fwrite(&tx.flags, 1, sizeof(tx.flags), texinfofile);
count++;

View File

@ -23,7 +23,7 @@ set(VIS_SOURCES
${VIS_INCLUDES})
add_executable(vis ${VIS_SOURCES})
target_link_libraries (vis ${CMAKE_THREAD_LIBS_INIT})
target_link_libraries (vis ${CMAKE_THREAD_LIBS_INIT} fmt::fmt)
find_library(M_LIB m)
if (M_LIB)
target_link_libraries (vis ${M_LIB})