First attempt at areaportals - sadly it seems like this method might not work with this compiler.

This commit is contained in:
Jonathan 2021-09-28 13:17:27 -04:00
parent 1a5dc9934f
commit 74bbce9eab
5 changed files with 278 additions and 14 deletions

View File

@ -102,12 +102,15 @@ public:
int firstoutputfacenumber;
int outputmodelnumber;
int32_t areaportalnum;
std::array<int32_t, 2> portalareas;
const mapbrush_t &mapbrush(int i) const;
mapentity_t()
: firstmapbrush(0), nummapbrushes(0), solid(nullptr), sky(nullptr), detail(nullptr),
detail_illusionary(nullptr), detail_fence(nullptr), liquid(nullptr), epairs(), brushes(nullptr),
numbrushes(0), firstoutputfacenumber(-1), outputmodelnumber(-1)
numbrushes(0), firstoutputfacenumber(-1), outputmodelnumber(-1), areaportalnum(0), portalareas({})
{
VectorSet(origin, 0, 0, 0);
}
@ -161,6 +164,8 @@ struct mapdata_t
std::vector<uint32_t> exported_leafbrushes;
std::vector<q2_dbrushside_qbism_t> exported_brushsides;
std::vector<dbrush_t> exported_brushes;
std::vector<dareaportal_t> exported_areaportals;
std::vector<darea_t> exported_areas;
std::string exported_entities;
std::string exported_texdata;
@ -170,6 +175,9 @@ struct mapdata_t
bool needslmshifts = false;
std::vector<uint8_t> exported_bspxbrushes;
// Q2 stuff
int32_t numareaportals;
// helpers
const std::string &miptexTextureName(int mt) const { return miptex.at(mt).name; }
@ -183,7 +191,9 @@ bool ParseEntity(parser_t &parser, mapentity_t *entity);
void EnsureTexturesLoaded();
void ProcessExternalMapEntity(mapentity_t *entity);
void ProcessAreaPortal(mapentity_t *entity);
bool IsWorldBrushEntity(const mapentity_t *entity);
bool IsNonRemoveWorldBrushEntity(const mapentity_t *entity);
void LoadMapFile(void);
mapentity_t LoadExternalMap(const char *filename);
void ConvertMapFile(void);

View File

@ -197,6 +197,7 @@ struct node_t
// are detail.
uint32_t firstleafbrush; // Q2
uint32_t numleafbrushes;
int32_t area;
bool opaque() const;
};

View File

@ -1884,10 +1884,31 @@ void ProcessExternalMapEntity(mapentity_t *entity)
SetKeyValue(entity, "origin", "0 0 0");
}
void ProcessAreaPortal(mapentity_t *entity)
{
Q_assert(!options.fOnlyents);
const char *classname = ValueForKey(entity, "classname");
if (Q_strcasecmp(classname, "func_areaportal"))
return;
// areaportal entities move their brushes, but don't eliminate
// the entity
// FIXME: print entity ID/line number
if (entity->nummapbrushes != 1)
FError("func_areaportal can only be a single brush");
map.brushes[entity->firstmapbrush].contents = Q2_CONTENTS_AREAPORTAL;
map.faces[map.brushes[entity->firstmapbrush].firstface].contents = Q2_CONTENTS_AREAPORTAL;
entity->areaportalnum = ++map.numareaportals;
// set the portal number as "style"
SetKeyValue(entity, "style", std::to_string(map.numareaportals).c_str());
}
/*
* Special world entities are entities which have their brushes added to the
* world before being removed from the map. Currently func_detail and
* func_group.
* world before being removed from the map.
*/
bool IsWorldBrushEntity(const mapentity_t *entity)
{
@ -1914,6 +1935,20 @@ bool IsWorldBrushEntity(const mapentity_t *entity)
return false;
}
/**
* Some games need special entities that are merged into the world, but not
* removed from the map entirely.
*/
bool IsNonRemoveWorldBrushEntity(const mapentity_t *entity)
{
const char *classname = ValueForKey(entity, "classname");
if (!Q_strcasecmp(classname, "func_areaportal"))
return true;
return false;
}
/**
* Loads an external .map file.
*

View File

@ -280,6 +280,8 @@ static std::vector<std::tuple<size_t, face_t *>> AddBrushBevels(const brush_t *b
static void ExportBrushList(const mapentity_t *entity, node_t *node, uint32_t &brush_offset)
{
LogPrint(LOG_PROGRESS, "---- {} ----\n", __func__);
brush_state = {};
for (const brush_t *b = entity->brushes; b; b = b->next) {
@ -307,6 +309,218 @@ static void ExportBrushList(const mapentity_t *entity, node_t *node, uint32_t &b
LogPrint(LOG_STAT, " {:8} total leaf brushes\n", brush_state.total_leaf_brushes);
}
/*
=========================================================
FLOOD AREAS
=========================================================
*/
int32_t c_areas;
/*
===============
Portal_EntityFlood
The entity flood determines which areas are
"outside" on the map, which are then filled in.
Flowing from side s to side !s
===============
*/
static bool Portal_EntityFlood(const portal_t *p, int32_t s)
{
if (p->nodes[0]->planenum != PLANENUM_LEAF
|| p->nodes[1]->planenum != PLANENUM_LEAF)
Error ("Portal_EntityFlood: not a leaf");
// can never cross to a solid
if ( (p->nodes[0]->contents.native & CONTENTS_SOLID)
|| (p->nodes[1]->contents.native & CONTENTS_SOLID) )
return false;
// can flood through everything else
return true;
}
/*
=============
FloodAreas_r
=============
*/
static void FloodAreas_r(mapentity_t *entity, node_t *node)
{
if (node->contents.native == Q2_CONTENTS_AREAPORTAL)
{
// this node is part of an area portal;
// if the current area has allready touched this
// portal, we are done
if (entity->portalareas[0] == c_areas || entity->portalareas[1] == c_areas)
return;
// note the current area as bounding the portal
if (entity->portalareas[1])
{
// FIXME: entity #
LogPrint("WARNING: areaportal entity touches > 2 areas\n Node Bounds: {} -> {}\n",
VecStr(node->bounds.mins()), VecStr(node->bounds.maxs()));
return;
}
if (entity->portalareas[0])
entity->portalareas[1] = c_areas;
else
entity->portalareas[0] = c_areas;
return;
}
if (node->area)
return; // already got it
node->area = c_areas;
int32_t s;
for (portal_t *p = node->portals; p; p = p->next[s])
{
s = (p->nodes[1] == node);
#if 0
if (p->nodes[!s]->occupied)
continue;
#endif
if (!Portal_EntityFlood (p, s))
continue;
FloodAreas_r(entity, p->nodes[!s]);
}
}
/*
=============
FindAreas_r
Just decend the tree, and for each node that hasn't had an
area set, flood fill out from there
=============
*/
static void FindAreas_r(mapentity_t *entity, node_t *node)
{
if (node->planenum != PLANENUM_LEAF)
{
FindAreas_r(entity, node->children[0]);
FindAreas_r(entity, node->children[1]);
return;
}
if (node->area)
return; // already got it
if (node->contents.native & Q2_CONTENTS_SOLID)
return;
// FIXME: how to do this since the nodes are destroyed by this point?
//if (!node->occupied)
// return; // not reachable by entities
// area portals are always only flooded into, never
// out of
if (node->contents.native == Q2_CONTENTS_AREAPORTAL)
return;
c_areas++;
FloodAreas_r(entity, node);
}
/*
=============
SetAreaPortalAreas_r
Just decend the tree, and for each node that hasn't had an
area set, flood fill out from there
=============
*/
static void SetAreaPortalAreas_r(mapentity_t *entity, node_t *node)
{
if (node->planenum != PLANENUM_LEAF)
{
SetAreaPortalAreas_r(entity, node->children[0]);
SetAreaPortalAreas_r(entity, node->children[1]);
return;
}
if (node->contents.native != Q2_CONTENTS_AREAPORTAL)
return;
if (node->area)
return; // already set
node->area = entity->portalareas[0];
if (!entity->portalareas[1])
{
// FIXME: entity #
LogPrint("WARNING: areaportal entity doesn't touch two areas\n Node Bounds: {} -> {}\n", VecStr(entity->bounds.mins()), VecStr(entity->bounds.maxs()));
return;
}
}
/*
=============
FloodAreas
Mark each leaf with an area, bounded by CONTENTS_AREAPORTAL
=============
*/
static void FloodAreas(mapentity_t *entity, node_t *headnode)
{
LogPrint(LOG_PROGRESS, "---- {} ----\n", __func__);
FindAreas_r(entity, headnode);
SetAreaPortalAreas_r(entity, headnode);
LogPrint(LOG_STAT, "{:5} areas\n", c_areas);
}
/*
=============
EmitAreaPortals
=============
*/
static void EmitAreaPortals(node_t *headnode)
{
LogPrint(LOG_PROGRESS, "---- {} ----\n", __func__);
map.exported_areaportals.emplace_back();
map.exported_areas.emplace_back();
for (size_t i = 1; i <= c_areas; i++) {
darea_t &area = map.exported_areas.emplace_back();
area.firstareaportal = map.exported_areaportals.size();
for (auto &e : map.entities) {
if (!e.areaportalnum)
continue;
dareaportal_t &dp = map.exported_areaportals.emplace_back();
if (e.portalareas[0] == i)
{
dp.portalnum = e.areaportalnum;
dp.otherarea = e.portalareas[1];
}
else if (e.portalareas[1] == i)
{
dp.portalnum = e.areaportalnum;
dp.otherarea = e.portalareas[0];
}
}
area.numareaportals = map.exported_areaportals.size() - area.firstareaportal;
}
LogPrint(LOG_STAT, "{:5} numareas\n", map.exported_areas.size());
LogPrint(LOG_STAT, "{:5} numareaportals\n", map.exported_areaportals.size());
}
/*
===============
ProcessEntity
@ -327,7 +541,7 @@ static void ProcessEntity(mapentity_t *entity, const int hullnum)
* func_group and func_detail entities get their brushes added to the
* worldspawn
*/
if (IsWorldBrushEntity(entity))
if (IsWorldBrushEntity(entity) || IsNonRemoveWorldBrushEntity(entity))
return;
// Export a blank model struct, and reserve the index (only do this once, for all hulls)
@ -385,7 +599,9 @@ static void ProcessEntity(mapentity_t *entity, const int hullnum)
/* Load external .map and change the classname, if needed */
ProcessExternalMapEntity(source);
if (IsWorldBrushEntity(source)) {
ProcessAreaPortal(source);
if (IsWorldBrushEntity(source) || IsNonRemoveWorldBrushEntity(source)) {
Brush_LoadEntity(entity, source, hullnum);
}
}
@ -496,6 +712,13 @@ static void ProcessEntity(mapentity_t *entity, const int hullnum)
TJunc(entity, nodes);
}
// Area portals
if (options.target_game->id == GAME_QUAKE_II) {
FloodAreas(entity, nodes);
EmitAreaPortals(nodes);
}
FreeAllPortals(nodes);
}
@ -515,7 +738,6 @@ static void ProcessEntity(mapentity_t *entity, const int hullnum)
firstface = MakeFaceEdges(entity, nodes);
if (options.target_game->id == GAME_QUAKE_II) {
LogPrint(LOG_PROGRESS, "---- ExportBrushList ----\n");
ExportBrushList(entity, nodes, brush_offset);
}
@ -563,7 +785,7 @@ static void UpdateEntLump(void)
if (!isBrushEnt)
continue;
if (IsWorldBrushEntity(entity))
if (IsWorldBrushEntity(entity) || IsNonRemoveWorldBrushEntity(entity))
continue;
snprintf(modname, sizeof(modname), "*%d", modnum);
@ -774,7 +996,7 @@ static void BSPX_CreateBrushList(void)
/* Load external .map and change the classname, if needed */
ProcessExternalMapEntity(source);
if (IsWorldBrushEntity(source)) {
if (IsWorldBrushEntity(source) || IsNonRemoveWorldBrushEntity(source)) {
Brush_LoadEntity(ent, source, -1);
}
}

View File

@ -416,6 +416,8 @@ static void WriteBSPFile()
bsp.dleafbrushes = std::move(map.exported_leafbrushes);
bsp.dbrushsides = std::move(map.exported_brushsides);
bsp.dbrushes = std::move(map.exported_brushes);
bsp.dareaportals = std::move(map.exported_areaportals);
bsp.dareas = std::move(map.exported_areas);
bsp.dentdata = std::move(map.exported_entities);
CopyString(map.exported_texdata, false, &bsp.texdatasize, (void **)&bsp.dtexdata);
@ -427,12 +429,6 @@ static void WriteBSPFile()
BSPX_AddLump(&bspdata, "BRUSHLIST", map.exported_bspxbrushes.data(), map.exported_bspxbrushes.size());
}
// FIXME: temp
bsp.dareaportals.push_back({});
bsp.dareas.push_back({});
bsp.dareas.push_back({ 0, 1 });
if (!ConvertBSPFormat(&bspdata, options.target_version)) {
const bspversion_t *extendedLimitsFormat = options.target_version->extended_limits;