diff --git a/.gitignore b/.gitignore index 1c9957c..230983d 100644 --- a/.gitignore +++ b/.gitignore @@ -100,3 +100,5 @@ Temporary Items !/tools/wad.cfg *.zip + +/build*/ \ No newline at end of file diff --git a/src/sdhlt/sdHLVIS/vis.cpp b/src/sdhlt/sdHLVIS/vis.cpp index e2fbc55..f629857 100644 --- a/src/sdhlt/sdHLVIS/vis.cpp +++ b/src/sdhlt/sdHLVIS/vis.cpp @@ -16,10 +16,13 @@ #ifdef SYSTEM_WIN32 #define WIN32_LEAN_AND_MEAN #include +// std:clamp() is at least MVSC 19 +#define CLAMP(x, min, max) x <= min ? min : x >= max ? max : x #endif #ifdef SYSTEM_POSIX #include +#define CLAMP(x, min, max) std::clamp(x, min, max) #endif #ifdef ZHLT_NETVIS @@ -75,6 +78,9 @@ overview_t g_overview[g_overview_max]; int g_overview_count = 0; leafinfo_t* g_leafinfos = NULL; +const int g_room_max = MAX_MAP_ENTITIES; +room_t g_room[g_room_max]; +int g_room_count = 0; static int totalvis = 0; @@ -515,6 +521,35 @@ static void LeafThread(int unused) #pragma warning(pop) #endif +// Recursively add `add` to `current` visibility leaf. +std::unordered_map leaf_flow_add_exclude = {}; +static void LeafFlowNeighborAddLeaf(const int current, const int add, const int neighbor) +{ + auto outbuffer = g_uncompressed + current * g_bitbytes; + + outbuffer[add >> 3] |= (1 << (add & 7)); + leaf_flow_add_exclude[current] = true; + + if (neighbor == 0) + { + return; + } + + auto leaf = &g_leafs[current]; + + for (int i = 0; i < leaf->numportals; i++) + { + auto p = leaf->portals[i]; + + if (leaf_flow_add_exclude[p->leaf]) { + // Log("leaf %d neighbor %d is excluded\n", current, p->leaf); + continue; + } + + LeafFlowNeighborAddLeaf(p->leaf, add, neighbor - 1); + } +} + // ===================================================================================== // LeafFlow // Builds the entire visibility list for a leaf @@ -589,6 +624,7 @@ static void LeafFlow(const int leafnum) outbuffer[i >> 3] |= (1 << (i & 7)); } } + numvis = 0; for (i = 0; i < g_portalleafs; i++) { @@ -807,6 +843,19 @@ static void CalcVis() CalcPortalVis(); + // Add additional leaves to the uncompressed vis. + for (i = 0; i < g_portalleafs; i++) + { + if (!g_leafinfos[i].additional_leaves.empty()) + { + for (int leaf : g_leafinfos[i].additional_leaves) + { + LeafFlowNeighborAddLeaf(i, leaf, g_leafinfos[i].neighbor); + leaf_flow_add_exclude.clear(); + } + } + } + // // assemble the leaf vis lists by oring and compressing the portal lists // @@ -948,6 +997,25 @@ static void LoadPortals(char* portal_image) } } } + + for (j = 0; j < g_room_count; j++) + { + int d1 = g_room[j].visleafnum - g_leafstarts[i]; + + if (0 <= d1 && d1 < g_leafcounts[i]) + { + for (int k = 0; k < g_portalleafs; k++) + { + int d2 = g_room[j].target_visleafnum - g_leafstarts[k]; + + if (0 <= d2 && d2 < g_leafcounts[k]) + { + g_leafinfos[i].additional_leaves.push_back(k); + g_leafinfos[i].neighbor = g_room[j].neighbor; + } + } + } + } } for (i = 0, p = g_portals; i < g_numportals; i++) { @@ -1788,7 +1856,10 @@ int main(const int argc, char** argv) int i; for (i = 0; i < g_numentities; i++) { - if (!strcmp (ValueForKey (&g_entities[i], "classname"), "info_overview_point")) + const char* current_entity_classname = ValueForKey (&g_entities[i], "classname"); + + if (!strcmp (current_entity_classname, "info_overview_point") + ) { if (g_overview_count < g_overview_max) { @@ -1800,6 +1871,53 @@ int main(const int argc, char** argv) g_overview_count++; } } + + else if (!strcmp (current_entity_classname, "info_portal")) + { + if (g_room_count < g_room_max) + { + vec3_t room_origin; + + GetVectorForKey (&g_entities[i], "origin", room_origin); + g_room[g_room_count].visleafnum = VisLeafnumForPoint (room_origin); + g_room[g_room_count].neighbor = CLAMP(IntForKey (&g_entities[i], "neighbor"), 0, MAX_ROOM_NEIGHBOR); + + const char* target = ValueForKey (&g_entities[i], "target"); + + if (strlen(target) == 0) + { + continue; + } + + bool has_target = false; + + // Find the target entity. + // Rewalk yes, very sad. + for (int j = 0; j < g_numentities; j++) + { + const char* current_entity_classname_nested = ValueForKey (&g_entities[j], "classname"); + + // Find a `info_leaf` and check if its targetname matches our target + if (!strcmp (current_entity_classname_nested, "info_leaf") + && !strcmp(ValueForKey (&g_entities[j], "targetname"), target)) + { + vec3_t room_target_origin; + + GetVectorForKey (&g_entities[j], "origin", room_target_origin); + g_room[g_room_count].target_visleafnum = VisLeafnumForPoint (room_target_origin); + + has_target = true; + } + } + + if (!has_target) + { + Warning("Entity %d (info_portal) does not have a target leaf.", i); + } + + g_room_count++; + } + } } } LoadPortalsByFilename(portalfile); diff --git a/src/sdhlt/sdHLVIS/vis.h b/src/sdhlt/sdHLVIS/vis.h index 7b73212..e864adb 100644 --- a/src/sdhlt/sdHLVIS/vis.h +++ b/src/sdhlt/sdHLVIS/vis.h @@ -18,6 +18,9 @@ #include "zones.h" #include "cmdlinecfg.h" +#include +#include + #define DEFAULT_MAXDISTANCE_RANGE 0 @@ -143,6 +146,25 @@ extern unsigned g_portalleafs; extern unsigned int g_maxdistance; //extern bool g_postcompile; + +// This allows the current leaf to have portal to selected leaf. +// TODO: vector for target so it can do a lot. Though doing the entity won't be as simple. +// That means we need to parse string and what not. +// For the time being, ONE target is good enough. +#define MAX_ROOM_NEIGHBOR 16 +typedef struct +{ + int visleafnum; + int target_visleafnum; + // Traversal of neighbors being affected. + int neighbor; +} +room_t; +extern const int g_room_max; +extern room_t g_room[]; +extern int g_room_count; +extern std::unordered_map leaf_flow_add_exclude; + typedef struct { vec3_t origin; @@ -158,6 +180,9 @@ typedef struct { bool isoverviewpoint; bool isskyboxpoint; + // For info_portal + std::vector additional_leaves; + int neighbor; } leafinfo_t; extern leafinfo_t *g_leafinfos; diff --git a/tools/sdhlt.fgd b/tools/sdhlt.fgd index 4971e84..4847b40 100644 --- a/tools/sdhlt.fgd +++ b/tools/sdhlt.fgd @@ -77,15 +77,31 @@ // info_overview_point // It makes all entities visible from this place. This is useful for overview mode (dev_overview 1). // If "Reversed" is selected, this place will become visible from the entire map. This is useful for large skybox model. -@PointClass color(255 0 0) = info_overview_point : "Disable VIS here for overview" +@PointClass color(255 0 0) = info_overview_point : "Disable VIS here by creating portals to every other leaf. Lag/wallhack warning. If reversed, every other leaf has a portal to this one." [ - reverse(choices) : "Reversed" : "" = + reverse(choices) : "Reversed (3D skybox)" : "" = [ "": "No" 1: "Yes" ] ] +// info_portal +// TODO: vector for target so it can do a lot. Though doing the entity won't be as simple. +// That means we need to parse string and what not. +// For the time being, ONE target is good enough. +@PointClass color(255 200 200) = info_portal : "Create a portal to the selected info_leaf, from the leaf the info_portal is inside. Forces target leaf to be visible from the current one." +[ + target(target_source) : "Name of info_leaf" + neighbor(integer) : "TODO: Layers of neighboring leaves to be affected" : 1 +] + +// info_leaf +@PointClass color(200 200 255) = info_leaf : "Works with info_portal. Used to select a leaf the info_leaf is inside." +[ + targetname(target_destination) : "Name" +] + // info_sunlight // It generates a fake light_environment which defines sv_skycolor and sv_skyvec in game. // If you are using multiple light_environments, you will probably need this entity.