From d38b88fbb9c06cba0fba9b6a9e3509862ecb8d33 Mon Sep 17 00:00:00 2001 From: NepDisk Date: Sat, 7 Mar 2026 22:06:44 -0500 Subject: [PATCH 01/18] Warning cleanup --- src/lua_hudlib.c | 1 + src/r_draw_column.cpp | 6 +++--- src/r_main.cpp | 3 --- src/r_patchrotation.c | 1 + src/r_things.cpp | 4 ++-- 5 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/lua_hudlib.c b/src/lua_hudlib.c index edc324c24..2f2c304f7 100644 --- a/src/lua_hudlib.c +++ b/src/lua_hudlib.c @@ -748,6 +748,7 @@ static int libd_drawAffine(lua_State *L) lua_getfield(L, LUA_REGISTRYINDEX, "HUD_DRAW_LIST"); huddrawlist_h list = lua_touserdata(L, -1); + (void)list; lua_pop(L, 1); //if (LUA_HUD_IsDrawListValid(list)) diff --git a/src/r_draw_column.cpp b/src/r_draw_column.cpp index 5aa3b9195..f9a3b9aae 100644 --- a/src/r_draw_column.cpp +++ b/src/r_draw_column.cpp @@ -589,9 +589,9 @@ static void R_DrawAffineColumnTemplate(drawcolumndata_t *dc) intptr_t frac; // Looks familiar. const intptr_t fracstep = dc->iscale; - const intptr_t heightmask = dc->sourcelength-1; // CPhipps - specify type - constexpr INT32 npow2min = -1; - const INT32 npow2max = dc->sourcelength; + //const intptr_t heightmask = dc->sourcelength-1; // CPhipps - specify type + //constexpr INT32 npow2min = -1; + //const INT32 npow2max = dc->sourcelength; // Framebuffer destination address. // SoM: MAGIC diff --git a/src/r_main.cpp b/src/r_main.cpp index 1f6e9f080..d40a377d1 100644 --- a/src/r_main.cpp +++ b/src/r_main.cpp @@ -202,9 +202,6 @@ consvar_t cv_fakerollangle = CVAR_INIT ("fakerollangle", "Off", CV_SAVE, CV_OnOf consvar_t cv_affineprescale = CVAR_INIT ("affineprescale", "Off", CV_SAVE, CV_OnOff, NULL); consvar_t cv_affinemosaic = CVAR_INIT ("affinemosaic", "Off", CV_SAVE, CV_OnOff, NULL); -static CV_PossibleValue_t affineangle_cons_t[] = {{0, "MIN"}, {360 * FRACUNIT, "MAX"}, {0, NULL}}; -static CV_PossibleValue_t affinetest_cons_t[] = {{0, "Off"}, {1, "On"}, {2, "Auto"}, {0, NULL}}; - void SplitScreen_OnChange(void) { UINT8 i; diff --git a/src/r_patchrotation.c b/src/r_patchrotation.c index ca854dcaf..581ce37af 100644 --- a/src/r_patchrotation.c +++ b/src/r_patchrotation.c @@ -164,6 +164,7 @@ vector2_t* R_RotateSpriteOffsetsByPitchRoll( { fixed_t rotcos, rotsin, finx, finy; vector2_t xvec, yvec; + (void)affine; // input offsets fixed_t xoffs, yoffs, xpiv, ypiv; diff --git a/src/r_things.cpp b/src/r_things.cpp index 289f97c95..15843c241 100644 --- a/src/r_things.cpp +++ b/src/r_things.cpp @@ -2081,7 +2081,7 @@ static void R_ProjectSprite(mobj_t *thing) // Affines boolean affinesprite = (((thing->player != NULL) || R_ThingIsAffineSprite(thing)) && (!splat)); affine_t affine_transform = {0}; - affine_bounding_t affine_bounds = {0}; + affine_bounding_t affine_bounds = {}; vector2_t affine_scale = {0}; vector2_t affine_distscale = {0}; f_vector2_t affine_pivotoffsetdiff = {0}; @@ -2928,7 +2928,7 @@ static void R_ProjectSprite(mobj_t *thing) vis->viewpoint.z = viewz; vis->viewpoint.angle = viewangle; - vis->affine = {0}; + vis->affine = {}; if (affinesprite) { From 440ccb67a653279aae6ab43032925bbeb321382e Mon Sep 17 00:00:00 2001 From: NepDisk Date: Sat, 7 Mar 2026 22:40:53 -0500 Subject: [PATCH 02/18] Remove extra deadzone adjustment at 0 --- src/g_game.c | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/src/g_game.c b/src/g_game.c index d3755faa2..549aea796 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -1451,17 +1451,8 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer) return; } - fixed_t deadZoneY = cv_deadzoney[forplayer].value; - const double deadZoneYDecimal = (double) deadZoneY / FRACUNIT; joystickvector.xaxis = G_PlayerInputAnalog(forplayer, gc_turnright, false, DEADZONE_X) - G_PlayerInputAnalog(forplayer, gc_turnleft, false, DEADZONE_X); - if (deadZoneYDecimal <= 0) - { - joystickvector.yaxis = G_PlayerInputAnalog(forplayer, gc_aimbackward, false, DEADZONE_Y) - G_PlayerInputAnalog(forplayer, gc_aimforward, false, DEADZONE_Y); - } - else - { - joystickvector.yaxis = 0; - } + joystickvector.yaxis = 0; G_HandleAxisDeadZone(forplayer, &joystickvector); // For kart, I've turned the aim axis into a digital axis because we only @@ -1469,11 +1460,7 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer) // This mean that the turn axis will still be gradient but up/down will be 0 // until the stick is pushed far enough // - // When the deadzone is set to 0 or lower, the aim axis returns to analog to avoid breaking the chasecam and freecam controls - if (deadZoneYDecimal > 0) - { - joystickvector.yaxis = G_PlayerInputAnalog(forplayer, gc_aimbackward, false, DEADZONE_Y) - G_PlayerInputAnalog(forplayer, gc_aimforward, false, DEADZONE_Y); - } + joystickvector.yaxis = G_PlayerInputAnalog(forplayer, gc_aimbackward, false, DEADZONE_Y) - G_PlayerInputAnalog(forplayer, gc_aimforward, false, DEADZONE_Y); if (encoremode) { From ad95233529791f1a40d4c47ce8c24252ebbd4eef Mon Sep 17 00:00:00 2001 From: yamamama Date: Sun, 8 Mar 2026 00:40:57 -0500 Subject: [PATCH 03/18] Fix object sloperoll issues --- src/p_user.c | 4 ++-- src/r_main.cpp | 27 +++++++++++++++++++++++++++ src/r_main.h | 2 ++ src/r_patchrotation.c | 4 ++-- 4 files changed, 33 insertions(+), 4 deletions(-) diff --git a/src/p_user.c b/src/p_user.c index f5a740e10..b12c21904 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -3884,8 +3884,8 @@ static angle_t P_GetCameraPitchRollAngle(mobj_t *mobj, player_t *viewPlayer) } else { - // Regular Mobjs don't air tilt. - viewingAngle = mobj->angle; + // For regular objects, use the camera; just not the *player's* camera. + viewingAngle = R_PointToAngleFloat(mobj->x, mobj->y); } pitchMul = -FINESINE(viewingAngle >> ANGLETOFINESHIFT); diff --git a/src/r_main.cpp b/src/r_main.cpp index d40a377d1..bc31300ef 100644 --- a/src/r_main.cpp +++ b/src/r_main.cpp @@ -14,6 +14,7 @@ /// See tables.c, too. #include +#include #include "doomdef.h" #include "g_game.h" @@ -51,6 +52,9 @@ extern consvar_t cv_debugrender_freezebsp; // Fineangles in the SCREENWIDTH wide window. #define FIELDOFVIEW 2048 +// Double-precision Pi value +#define DOUBLE_PI 3.141592653589793 + // increment every time a check is made size_t validcount = 1; @@ -387,6 +391,29 @@ angle_t R_PointToAngle2(fixed_t pviewx, fixed_t pviewy, fixed_t x, fixed_t y) 0; } +#define ANGLE_180_DOUBLE (static_cast(ANGLE_180)) +#define ANGLE_360_DOUBLE (ANGLE_180_DOUBLE * 2) + +// For absolute accuracy. Please don't use this outside of renderers. +angle_t R_PointToAngleFloat2(fixed_t x1, fixed_t y1, fixed_t x2, fixed_t y2) +{ + double deltax = FixedToDouble(x2 - x1); + double deltay = FixedToDouble(y2 - y1); + + // Converted to degrees + double _arctan = (atan2(deltay, deltax) * ANGLE_180_DOUBLE) / DOUBLE_PI; + + while (_arctan < 0) + { + _arctan += ANGLE_360_DOUBLE; + } + + return static_cast(_arctan); +} + +#undef ANGLE_180_DOUBLE +#undef ANGLE_360_DOUBLE + // // R_ScaleFromGlobalAngle // Returns the texture mapping scale for the current line (horizontal span) diff --git a/src/r_main.h b/src/r_main.h index 26bcecf97..dbc0b89c6 100644 --- a/src/r_main.h +++ b/src/r_main.h @@ -83,9 +83,11 @@ FUNCINLINE static ATTRINLINE INT32 R_PointOnSideFast(fixed_t x, fixed_t y, const INT32 R_PointOnSegSide(fixed_t x, fixed_t y, const seg_t *line); #define R_PointToAngle(x, y) R_PointToAngle2(viewx, viewy, x, y) +#define R_PointToAngleFloat(x, y) (R_PointToAngleFloat2(viewx, viewy, x, y)) angle_t R_PointToAnglePlayer(player_t *player, fixed_t x, fixed_t y); angle_t R_PointToAngle64(INT64 x, INT64 y); angle_t R_PointToAngle2(fixed_t px2, fixed_t py2, fixed_t px1, fixed_t py1); +angle_t R_PointToAngleFloat2(fixed_t x1, fixed_t y1, fixed_t x2, fixed_t y2); #define R_PointToDist(x, y) R_PointToDist2(viewx, viewy, x, y) #define R_PointToDist2(px2, py2, px1, py1) FixedHypot((px1) - (px2), (py1) - (py2)) diff --git a/src/r_patchrotation.c b/src/r_patchrotation.c index 581ce37af..251cfe141 100644 --- a/src/r_patchrotation.c +++ b/src/r_patchrotation.c @@ -38,8 +38,8 @@ angle_t R_GetPitchRollAngle(mobj_t *mobj, player_t *viewPlayer, interpmobjstate_ } else { - // Regular Mobjs don't air tilt. - viewingAngle = mobj->angle; + // For regular objects, use the camera; just not the *player's* camera. + viewingAngle = R_PointToAngleFloat(mobj->x, mobj->y); } pitchMul = -FINESINE(viewingAngle >> ANGLETOFINESHIFT); From 1ddd84cf139264f41e22b3de92e3590824c5c85d Mon Sep 17 00:00:00 2001 From: Alug Date: Wed, 24 Jan 2024 03:41:02 +0100 Subject: [PATCH 04/18] more pronounced ripple eeeh kinda close ig --- src/hardware/hw_shaders.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/hardware/hw_shaders.h b/src/hardware/hw_shaders.h index 3978f6207..4abf8e3ad 100644 --- a/src/hardware/hw_shaders.h +++ b/src/hardware/hw_shaders.h @@ -283,16 +283,16 @@ #define GLSL_WATER_TEXEL \ "float water_z = (gl_FragCoord.z / gl_FragCoord.w) / 2.0;\n" \ "float a = -pi * (water_z * freq) + (leveltime * speed);\n" \ - "float sdistort = sin(a) * amp;\n" \ - "float cdistort = cos(a) * amp;\n" \ + "float sdistort = sin(a) * amp * 1.5;\n" \ + "float cdistort = cos(a) * amp * 2.2;\n" \ "vec4 texel = texture2D(tex, vec2(gl_TexCoord[0].s - sdistort, gl_TexCoord[0].t - cdistort));\n" #define GLSL_WATER_FRAGMENT_SHADER \ "#version 120\n" \ GLSL_FLOOR_FUDGES \ - "const float freq = 0.025;\n" \ + "const float freq = 0.03;\n" \ "const float amp = 0.025;\n" \ - "const float speed = 2.0;\n" \ + "const float speed = 1.6;\n" \ "const float pi = 3.14159;\n" \ "#ifdef SRB2_PALETTE_RENDERING\n" \ "uniform sampler2D tex;\n" \ From baf749e5c3ebae915011fa94f4bb49080457b462 Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Sun, 8 Mar 2026 13:15:13 -0400 Subject: [PATCH 05/18] Lua colorlib: extracolormap support --- src/CMakeLists.txt | 1 + src/hardware/hw_defs.h | 3 - src/hardware/hw_main.c | 8 +- src/hardware/hw_shaders.h | 2 +- src/lua_baselib.c | 6 +- src/lua_colorlib.c | 334 ++++++++++++++++++++++++++++++++++++++ src/lua_libs.h | 3 + src/lua_maplib.c | 5 + src/lua_script.c | 1 + src/m_misc.cpp | 34 ++++ src/m_misc.h | 4 + src/r_data.c | 244 +++++++++++----------------- src/r_data.h | 1 + 13 files changed, 486 insertions(+), 160 deletions(-) create mode 100644 src/lua_colorlib.c diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index dd7eb65ee..5f81a30f4 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -118,6 +118,7 @@ add_executable(BLANKART MACOSX_BUNDLE WIN32 lua_botvarslib.c lua_hudlib.c lua_hudlib_drawlist.c + lua_colorlib.c lua_followerlib.c lua_terrainlib.c lua_waypointslib.c diff --git a/src/hardware/hw_defs.h b/src/hardware/hw_defs.h index b2843728d..381f13a57 100644 --- a/src/hardware/hw_defs.h +++ b/src/hardware/hw_defs.h @@ -299,9 +299,6 @@ struct FSurfaceInfo }; typedef struct FSurfaceInfo FSurfaceInfo; -#define GL_DEFAULTMIX 0x00000000 -#define GL_DEFAULTFOG 0x19000000 - // Various settings and states for the rendering backend. enum hwdsetspecialstate { diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c index ea9bfb591..e45f36ffb 100644 --- a/src/hardware/hw_main.c +++ b/src/hardware/hw_main.c @@ -398,8 +398,8 @@ void HWR_Lighting(FSurfaceInfo *Surface, INT32 light_level, extracolormap_t *col RGBA_t poly_color, tint_color, fade_color; poly_color.rgba = 0xFFFFFFFF; - tint_color.rgba = (colormap != NULL) ? (UINT32)colormap->rgba : GL_DEFAULTMIX; - fade_color.rgba = (colormap != NULL) ? (UINT32)colormap->fadergba : GL_DEFAULTFOG; + tint_color.rgba = (colormap != NULL) ? (UINT32)colormap->rgba : 0x00000000; + fade_color.rgba = (colormap != NULL) ? (UINT32)colormap->fadergba : 0xFF000000; // Crappy backup coloring if you can't do shaders if (!HWR_UseShader()) @@ -413,7 +413,7 @@ void HWR_Lighting(FSurfaceInfo *Surface, INT32 light_level, extracolormap_t *col blue = (float)poly_color.s.blue; // 48 is just an arbritrary value that looked relatively okay. - tint_alpha = (float)(sqrt(tint_color.s.alpha) * 48) / 255.0f; + tint_alpha = (float)(sqrt((float)tint_color.s.alpha / 10.2) * 48) / 255.0f; // 8 is roughly the brightness of the "close" color in Software, and 16 the brightness of the "far" color. // 8 is too bright for dark levels, and 16 is too dark for bright levels. @@ -469,7 +469,7 @@ UINT8 HWR_FogBlockAlpha(INT32 light, extracolormap_t *colormap) // Let's see if RGBA_t realcolor, surfcolor; INT32 alpha; - realcolor.rgba = (colormap != NULL) ? colormap->rgba : GL_DEFAULTMIX; + realcolor.rgba = (colormap != NULL) ? colormap->rgba : 0x00000000; if (HWR_UseShader()) { diff --git a/src/hardware/hw_shaders.h b/src/hardware/hw_shaders.h index 4abf8e3ad..e92cc969f 100644 --- a/src/hardware/hw_shaders.h +++ b/src/hardware/hw_shaders.h @@ -101,7 +101,7 @@ #define GLSL_SOFTWARE_TINT_EQUATION \ "if (mix(tint_color.a, 0.0, brightmap_mix) > 0.0) {\n" \ "float color_bright = sqrt((base_color.r * base_color.r) + (base_color.g * base_color.g) + (base_color.b * base_color.b));\n" \ - "float strength = sqrt(9.0 * mix(tint_color.a, 0.0, brightmap_mix));\n" \ + "float strength = sqrt(mix(tint_color.a, 0.0, brightmap_mix));\n" \ "final_color.r = clamp((color_bright * (tint_color.r * strength)) + (base_color.r * (1.0 - strength)), 0.0, 1.0);\n" \ "final_color.g = clamp((color_bright * (tint_color.g * strength)) + (base_color.g * (1.0 - strength)), 0.0, 1.0);\n" \ "final_color.b = clamp((color_bright * (tint_color.b * strength)) + (base_color.b * (1.0 - strength)), 0.0, 1.0);\n" \ diff --git a/src/lua_baselib.c b/src/lua_baselib.c index d38d5d41d..63f9c331f 100644 --- a/src/lua_baselib.c +++ b/src/lua_baselib.c @@ -232,6 +232,8 @@ static const struct { {META_PATCH, "patch_t"}, {META_COLORMAP, "colormap"}, + {META_EXTRACOLORMAP,"extracolormap_t"}, + {META_LIGHTTABLE, "lighttable_t"}, {META_CAMERA, "camera_t"}, {META_ACTION, "action"}, @@ -1793,7 +1795,6 @@ static int lib_pGetSectorLightLevelAt(lua_State *L) return 1; } -/* Maybe color lib someday.... static int lib_pGetSectorColormapAt(lua_State *L) { boolean has_sector = false; @@ -1817,7 +1818,6 @@ static int lib_pGetSectorColormapAt(lua_State *L) LUA_PushUserdata(L, exc, META_EXTRACOLORMAP); return 1; } -*/ static int lib_pDoSpring(lua_State *L) { @@ -5589,7 +5589,7 @@ static luaL_Reg lib[] = { {"P_FloorzAtPos",lib_pFloorzAtPos}, {"P_CeilingzAtPos",lib_pCeilingzAtPos}, {"P_GetSectorLightLevelAt",lib_pGetSectorLightLevelAt}, - //{"P_GetSectorColormapAt",lib_pGetSectorColormapAt}, + {"P_GetSectorColormapAt",lib_pGetSectorColormapAt}, {"P_DoSpring",lib_pDoSpring}, // p_inter diff --git a/src/lua_colorlib.c b/src/lua_colorlib.c new file mode 100644 index 000000000..b60245f30 --- /dev/null +++ b/src/lua_colorlib.c @@ -0,0 +1,334 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2021-2022 by "Lactozilla". +// Copyright (C) 2014-2023 by Sonic Team Junior. +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- +/// \file lua_colorlib.c +/// \brief color and colormap libraries for Lua scripting + +#include "doomdef.h" +#include "fastcmp.h" +#include "r_data.h" + +#include "lua_script.h" +#include "lua_libs.h" + +#include "k_color.h" + +#define IS_HEX_CHAR(x) ((x >= '0' && x <= '9') || (x >= 'a' && x <= 'f') || (x >= 'A' && x <= 'F')) +#define ARE_HEX_CHARS(str, i) IS_HEX_CHAR(str[i]) && IS_HEX_CHAR(str[i + 1]) + +static UINT32 hex2int(char x) +{ + if (x >= '0' && x <= '9') + return x - '0'; + else if (x >= 'a' && x <= 'f') + return x - 'a' + 10; + else if (x >= 'A' && x <= 'F') + return x - 'A' + 10; + + return 0; +} + +static UINT8 ParseHTMLColor(const char *str, UINT8 *rgba, size_t numc) +{ + const char *hex = str; + + if (hex[0] == '#') + hex++; + else if (!IS_HEX_CHAR(hex[0])) + return 0; + + size_t len = strlen(hex); + + if (len == 3) + { + // Shorthand like #09C + for (unsigned i = 0; i < 3; i++) + { + if (!IS_HEX_CHAR(hex[i])) + return 0; + + UINT32 hx = hex2int(hex[i]); + *rgba++ = (hx * 16) + hx; + } + + return 3; + } + else if (len == 6 || len == 8) + { + if (numc != 4) + len = 6; + + // A triplet like #0099CC + for (unsigned i = 0; i < len; i += 2) + { + if (!ARE_HEX_CHARS(hex, i)) + return false; + + *rgba++ = (hex2int(hex[i]) * 16) + hex2int(hex[i + 1]); + } + + return len; + } + + return 0; +} + +///////////////////////// +// extracolormap userdata +///////////////////////// + +enum extracolormap_e { + extracolormap_red = 0, + extracolormap_green, + extracolormap_blue, + extracolormap_alpha, + extracolormap_color, + extracolormap_fade_red, + extracolormap_fade_green, + extracolormap_fade_blue, + extracolormap_fade_alpha, + extracolormap_fade_color, + extracolormap_fade_start, + extracolormap_fade_end, + extracolormap_colormap +}; + +static const char *const extracolormap_opt[] = { + "red", + "green", + "blue", + "alpha", + "color", + "fade_red", + "fade_green", + "fade_blue", + "fade_alpha", + "fade_color", + "fade_start", + "fade_end", + "colormap", + NULL}; + +static int extracolormap_get(lua_State *L) +{ + extracolormap_t *exc = *((extracolormap_t **)luaL_checkudata(L, 1, META_EXTRACOLORMAP)); + enum extracolormap_e field = luaL_checkoption(L, 2, NULL, extracolormap_opt); + + switch (field) + { + case extracolormap_red: + lua_pushinteger(L, R_GetRgbaR(exc->rgba)); + break; + case extracolormap_green: + lua_pushinteger(L, R_GetRgbaG(exc->rgba)); + break; + case extracolormap_blue: + lua_pushinteger(L, R_GetRgbaB(exc->rgba)); + break; + case extracolormap_alpha: + lua_pushinteger(L, R_GetRgbaA(exc->rgba)); + break; + case extracolormap_color: + lua_pushinteger(L, R_GetRgbaR(exc->rgba)); + lua_pushinteger(L, R_GetRgbaG(exc->rgba)); + lua_pushinteger(L, R_GetRgbaB(exc->rgba)); + lua_pushinteger(L, R_GetRgbaA(exc->rgba)); + return 4; + case extracolormap_fade_red: + lua_pushinteger(L, R_GetRgbaR(exc->fadergba)); + break; + case extracolormap_fade_green: + lua_pushinteger(L, R_GetRgbaG(exc->fadergba)); + break; + case extracolormap_fade_blue: + lua_pushinteger(L, R_GetRgbaB(exc->fadergba)); + break; + case extracolormap_fade_alpha: + lua_pushinteger(L, R_GetRgbaA(exc->fadergba)); + break; + case extracolormap_fade_color: + lua_pushinteger(L, R_GetRgbaR(exc->fadergba)); + lua_pushinteger(L, R_GetRgbaG(exc->fadergba)); + lua_pushinteger(L, R_GetRgbaB(exc->fadergba)); + lua_pushinteger(L, R_GetRgbaA(exc->fadergba)); + return 4; + case extracolormap_fade_start: + lua_pushinteger(L, exc->fadestart); + break; + case extracolormap_fade_end: + lua_pushinteger(L, exc->fadeend); + break; + case extracolormap_colormap: + LUA_PushUserdata(L, exc->colormap, META_LIGHTTABLE); + break; + } + return 1; +} + +static void GetExtraColormapRGBA(lua_State *L, UINT8 *rgba, int arg) +{ + if (lua_type(L, arg) == LUA_TSTRING) + { + const char *str = lua_tostring(L, arg); + UINT8 parsed = ParseHTMLColor(str, rgba, 4); + if (!parsed) + luaL_error(L, "Malformed HTML color '%s'", str); + } + else + { + UINT32 colors = lua_tointeger(L, arg); + if (colors > 0xFFFFFF) + { + rgba[0] = (colors >> 24) & 0xFF; + rgba[1] = (colors >> 16) & 0xFF; + rgba[2] = (colors >> 8) & 0xFF; + rgba[3] = colors & 0xFF; + } + else + { + rgba[0] = (colors >> 16) & 0xFF; + rgba[1] = (colors >> 8) & 0xFF; + rgba[2] = colors & 0xFF; + rgba[3] = 0xFF; + } + } +} + +static int extracolormap_set(lua_State *L) +{ + extracolormap_t *exc = *((extracolormap_t **)luaL_checkudata(L, 1, META_EXTRACOLORMAP)); + enum extracolormap_e field = luaL_checkoption(L, 2, NULL, extracolormap_opt); + + UINT8 r = R_GetRgbaR(exc->rgba); + UINT8 g = R_GetRgbaG(exc->rgba); + UINT8 b = R_GetRgbaB(exc->rgba); + UINT8 a = R_GetRgbaA(exc->rgba); + + UINT8 fr = R_GetRgbaR(exc->fadergba); + UINT8 fg = R_GetRgbaG(exc->fadergba); + UINT8 fb = R_GetRgbaB(exc->fadergba); + UINT8 fa = R_GetRgbaA(exc->fadergba); + + UINT8 rgba[4]; + + INT32 old_rgba = exc->rgba, old_fade_rgba = exc->fadergba; // It's not unsigned? + UINT8 old_fade_start = exc->fadestart, old_fade_end = exc->fadeend; + +#define val luaL_checkinteger(L, 3) + + switch(field) + { + case extracolormap_red: + exc->rgba = R_PutRgbaRGBA(val, g, b, a); + break; + case extracolormap_green: + exc->rgba = R_PutRgbaRGBA(r, val, b, a); + break; + case extracolormap_blue: + exc->rgba = R_PutRgbaRGBA(r, g, val, a); + break; + case extracolormap_alpha: + exc->rgba = R_PutRgbaRGBA(r, g, b, val); + break; + case extracolormap_color: + rgba[0] = r; + rgba[1] = g; + rgba[2] = b; + rgba[3] = a; + GetExtraColormapRGBA(L, rgba, 3); + exc->rgba = R_PutRgbaRGBA(rgba[0], rgba[1], rgba[2], rgba[3]); + break; + case extracolormap_fade_red: + exc->fadergba = R_PutRgbaRGBA(val, fg, fb, fa); + break; + case extracolormap_fade_green: + exc->fadergba = R_PutRgbaRGBA(fr, val, fb, fa); + break; + case extracolormap_fade_blue: + exc->fadergba = R_PutRgbaRGBA(fr, fg, val, fa); + break; + case extracolormap_fade_alpha: + exc->fadergba = R_PutRgbaRGBA(fr, fg, fb, val); + break; + case extracolormap_fade_color: + rgba[0] = fr; + rgba[1] = fg; + rgba[2] = fb; + rgba[3] = fa; + GetExtraColormapRGBA(L, rgba, 3); + exc->fadergba = R_PutRgbaRGBA(rgba[0], rgba[1], rgba[2], rgba[3]); + break; + case extracolormap_fade_start: + if (val > 31) + return luaL_error(L, "fade start %d out of range (0 - 31)", val); + exc->fadestart = val; + break; + case extracolormap_fade_end: + if (val > 31) + return luaL_error(L, "fade end %d out of range (0 - 31)", val); + exc->fadeend = val; + break; + case extracolormap_colormap: + return luaL_error(L, LUA_QL("extracolormap_t") " field " LUA_QS " should not be set directly.", extracolormap_opt[field]); + } + +#undef val + + if (exc->rgba != old_rgba + || exc->fadergba != old_fade_rgba + || exc->fadestart != old_fade_start + || exc->fadeend != old_fade_end) + R_GenerateLightTable(exc, true); + + return 0; +} + +static int lighttable_get(lua_State *L) +{ + void **userdata; + + lighttable_t *table = *((lighttable_t **)luaL_checkudata(L, 1, META_LIGHTTABLE)); + UINT32 row = luaL_checkinteger(L, 2); + if (row < 1 || row > 34) + return luaL_error(L, "lighttable row %d out of range (1 - %d)", row, 34); + + userdata = lua_newuserdata(L, sizeof(void *)); + *userdata = &table[256 * (row - 1)]; + luaL_getmetatable(L, META_COLORMAP); + lua_setmetatable(L, -2); + + return 1; +} + +static int lighttable_len(lua_State *L) +{ + lua_pushinteger(L, NUM_PALETTE_ENTRIES); + return 1; +} + +int LUA_ColorLib(lua_State *L) +{ + luaL_newmetatable(L, META_EXTRACOLORMAP); + lua_pushcfunction(L, extracolormap_get); + lua_setfield(L, -2, "__index"); + + lua_pushcfunction(L, extracolormap_set); + lua_setfield(L, -2, "__newindex"); + lua_pop(L, 1); + + luaL_newmetatable(L, META_LIGHTTABLE); + lua_pushcfunction(L, lighttable_get); + lua_setfield(L, -2, "__index"); + + lua_pushcfunction(L, lighttable_len); + lua_setfield(L, -2, "__len"); + lua_pop(L, 1); + + return 0; +} diff --git a/src/lua_libs.h b/src/lua_libs.h index aaa9725fe..90fe68bca 100644 --- a/src/lua_libs.h +++ b/src/lua_libs.h @@ -96,6 +96,8 @@ extern lua_State *gL; #define META_HUDINFO "HUDINFO_T*" #define META_PATCH "PATCH_T*" #define META_COLORMAP "COLORMAP" +#define META_EXTRACOLORMAP "EXTRACOLORMAP_T*" +#define META_LIGHTTABLE "LIGHTTABLE_T*" #define META_CAMERA "CAMERA_T*" #define META_ACTION "actionf_p1*" @@ -152,6 +154,7 @@ int LUA_TagLib(lua_State *L); int LUA_PolyObjLib(lua_State *L); int LUA_BlockmapLib(lua_State *L); int LUA_HudLib(lua_State *L); +int LUA_ColorLib(lua_State *L); int LUA_FollowerLib(lua_State *L); int LUA_BotVarsLib(lua_State *L); int LUA_TerrainLib(lua_State *L); diff --git a/src/lua_maplib.c b/src/lua_maplib.c index 29a317876..532284e93 100644 --- a/src/lua_maplib.c +++ b/src/lua_maplib.c @@ -49,6 +49,7 @@ enum sector_e { sector_ffloors, sector_fslope, sector_cslope, + sector_colormap, sector_flags, sector_specialflags, sector_damagetype, @@ -85,6 +86,7 @@ static const char *const sector_opt[] = { "ffloors", "f_slope", "c_slope", + "colormap", "flags", "specialflags", "damagetype", @@ -750,6 +752,9 @@ static int sector_get(lua_State *L) case sector_cslope: // c_slope LUA_PushUserdata(L, sector->c_slope, META_SLOPE); return 1; + case sector_colormap: // extra_colormap + LUA_PushUserdata(L, sector->extra_colormap, META_EXTRACOLORMAP); + return 1; case sector_flags: // flags lua_pushinteger(L, sector->flags); return 1; diff --git a/src/lua_script.c b/src/lua_script.c index 0144e09d8..a23487eda 100644 --- a/src/lua_script.c +++ b/src/lua_script.c @@ -61,6 +61,7 @@ static lua_CFunction liblist[] = { LUA_PolyObjLib, // polyobj_t LUA_BlockmapLib, // blockmap stuff LUA_HudLib, // HUD stuff + LUA_ColorLib, // general color functions LUA_FollowerLib, // follower_t, followers[] LUA_BotVarsLib, // botvars_t LUA_TerrainLib, // t_splash_t, t_footstep_t, t_overlay_t, terrain_t diff --git a/src/m_misc.cpp b/src/m_misc.cpp index cb1789e19..50ce1ffa4 100644 --- a/src/m_misc.cpp +++ b/src/m_misc.cpp @@ -2556,3 +2556,37 @@ UINT32 FNV1a_QuickCaseHash(const char *message, size_t size) return hash; } + +// Returns true if the string is empty. +boolean M_IsStringEmpty(const char *s) +{ + const char *ch = s; + + if (s == NULL || s[0] == '\0') + return true; + + for (;;ch++) + { + if (!(*ch)) + break; + if (!isspace((*ch))) + return false; + } + + return true; +} + +// Rounds off floating numbers and checks for 0 - 255 bounds +int M_RoundUp(double number) +{ + if (number > 255.0l) + return 255; + if (number < 0.0l) + return 0; + + if ((int)number <= (int)(number - 0.5f)) + return (int)number + 1; + + return (int)number; +} + diff --git a/src/m_misc.h b/src/m_misc.h index 10c1eeed3..9e5dfea63 100644 --- a/src/m_misc.h +++ b/src/m_misc.h @@ -126,6 +126,10 @@ FUNCMATH UINT8 M_CountBits(UINT32 num, UINT8 size); // Hashes some message using FNV-1a UINT32 FNV1a_QuickCaseHash(const char *message, size_t size); +boolean M_IsStringEmpty(const char *s); + +int M_RoundUp(double number); + #include "w_wad.h" extern char configfile[MAX_WADPATH]; diff --git a/src/r_data.c b/src/r_data.c index b4ae9eeae..d17ba2901 100644 --- a/src/r_data.c +++ b/src/r_data.c @@ -387,7 +387,7 @@ extracolormap_t *R_CreateDefaultColormap(boolean lighttable) exc->fadeend = 31; exc->flags = 0; exc->rgba = 0; - exc->fadergba = 0x19000000; + exc->fadergba = 0xFF000000; exc->colormap = lighttable ? R_CreateLightTable(exc) : NULL; #ifdef EXTRACOLORMAPLUMPS exc->lump = LUMPERROR; @@ -502,7 +502,7 @@ boolean R_CheckDefaultColormapByValues(boolean checkrgba, boolean checkfadergba, && !flags) ) && (!checkrgba ? true : rgba == 0) - && (!checkfadergba ? true : fadergba == 0x19000000) + && (!checkfadergba ? true : (unsigned)fadergba == 0xFF000000) #ifdef EXTRACOLORMAPLUMPS && lump == LUMPERROR && extra_colormap->lumpname[0] == 0 @@ -603,7 +603,7 @@ extracolormap_t *R_ColormapForName(char *name) if (lump == LUMPERROR) I_Error("R_ColormapForName: Cannot find colormap lump %.8s\n", name); - exc = R_GetColormapFromListByValues(0, 0x19000000, 0, 31, 0, lump); + exc = R_GetColormapFromListByValues(0, 0xFF000000, 0, 31, 0, lump); if (exc) return exc; @@ -623,7 +623,7 @@ extracolormap_t *R_ColormapForName(char *name) exc->fadeend = 31; exc->flags = 0; exc->rgba = 0; - exc->fadergba = 0x19000000; + exc->fadergba = 0xFF000000; R_AddColormapToList(exc); @@ -642,27 +642,42 @@ extracolormap_t *R_ColormapForName(char *name) static double brightChange[256], map[256][3]; static double deltas[256][3]; // for old fade colormaps -static int RoundUp(double number); +static colorlookup_t lighttable_lut; + +static UINT8 LightTableNearest(UINT8 r, UINT8 g, UINT8 b) +{ + return NearestColor(r, g, b); +} + +static UINT8 LightTableNearest_LUT(UINT8 r, UINT8 g, UINT8 b) +{ + return GetColorLUT(&lighttable_lut, r, g, b); +} lighttable_t *R_CreateLightTable(extracolormap_t *extra_colormap) { - double cmaskr, cmaskg, cmaskb, cdestr, cdestg, cdestb, cdestbright; + extra_colormap->colormap = Z_MallocAlign((256 * 34) + 10, PU_LEVEL, NULL, 8); + R_GenerateLightTable(extra_colormap, false); + return extra_colormap->colormap; +} + +void R_GenerateLightTable(extracolormap_t *extra_colormap, boolean uselookup) +{ + double cmaskr, cmaskg, cmaskb, cdestr, cdestg, cdestb; double maskamt = 0, othermask = 0; - double fmaskamt = 0, fothermask = 0; UINT8 cr = R_GetRgbaR(extra_colormap->rgba), - cg = R_GetRgbaG(extra_colormap->rgba), - cb = R_GetRgbaB(extra_colormap->rgba), - ca = R_GetRgbaA(extra_colormap->rgba), - cfr = R_GetRgbaR(extra_colormap->fadergba), - cfg = R_GetRgbaG(extra_colormap->fadergba), - cfb = R_GetRgbaB(extra_colormap->fadergba), - cfa = R_GetRgbaA(extra_colormap->fadergba); + cg = R_GetRgbaG(extra_colormap->rgba), + cb = R_GetRgbaB(extra_colormap->rgba), + ca = R_GetRgbaA(extra_colormap->rgba), + cfr = R_GetRgbaR(extra_colormap->fadergba), + cfg = R_GetRgbaG(extra_colormap->fadergba), + cfb = R_GetRgbaB(extra_colormap->fadergba); + // cfa = R_GetRgbaA(extra_colormap->fadergba); // unused in software UINT8 fadestart = extra_colormap->fadestart, - fadedist = extra_colormap->fadeend - extra_colormap->fadestart; + fadedist = extra_colormap->fadeend - extra_colormap->fadestart; - lighttable_t *lighttable = NULL; size_t i; ///////////////////// @@ -672,7 +687,7 @@ lighttable_t *R_CreateLightTable(extracolormap_t *extra_colormap) cmaskg = cg; cmaskb = cb; - maskamt = (double)(ca/24.0l); + maskamt = (double)(ca/255.0l); othermask = 1 - maskamt; maskamt /= 0xff; @@ -686,27 +701,39 @@ lighttable_t *R_CreateLightTable(extracolormap_t *extra_colormap) cdestr = cfr; cdestg = cfg; cdestb = cfb; - cdestbright = sqrt((cfr*cfr) + (cfg*cfg) + (cfb*cfb)); - fmaskamt = (double)(cfa/24.0l); - fothermask = 1 - fmaskamt; - //fmaskamt /= 0xff; + // fade alpha unused in software + // maskamt = (double)(cfa/255.0l); + // othermask = 1 - maskamt; + // maskamt /= 0xff; - (void)fothermask; // unused, but don't feel like commenting it out + // cdestr *= maskamt; + // cdestg *= maskamt; + // cdestb *= maskamt; ///////////////////// // This code creates the colormap array used by software renderer ///////////////////// { - double r, g, b, cbrightness, cbest, cdist; + double r, g, b, cbrightness; int p; - lighttable_t *colormap_p; + char *colormap_p; + + UINT8 (*NearestColorFunc)(UINT8, UINT8, UINT8); + + if (uselookup) + { + InitColorLUT(&lighttable_lut, pMasterPalette, false); + NearestColorFunc = LightTableNearest_LUT; + } + else + NearestColorFunc = LightTableNearest; // Initialise the map and delta arrays // map[i] stores an RGB color (as double) for index i, // which is then converted to SRB2's palette later - // brightChange[i] is the value added/subtracted every step for the fade; - // map[i]'s values are in/decremented by it after each use + // deltas[i] stores a corresponding fade delta between the RGB color and the final fade color; + // map[i]'s values are decremented by after each use for (i = 0; i < 256; i++) { r = pMasterPalette[i].s.red; @@ -717,122 +744,55 @@ lighttable_t *R_CreateLightTable(extracolormap_t *extra_colormap) map[i][0] = (cbrightness * cmaskr) + (r * othermask); if (map[i][0] > 255.0l) map[i][0] = 255.0l; + deltas[i][0] = (map[i][0] - cdestr) / (double)fadedist; map[i][1] = (cbrightness * cmaskg) + (g * othermask); if (map[i][1] > 255.0l) map[i][1] = 255.0l; + deltas[i][1] = (map[i][1] - cdestg) / (double)fadedist; map[i][2] = (cbrightness * cmaskb) + (b * othermask); if (map[i][2] > 255.0l) map[i][2] = 255.0l; - - if (extra_colormap->flags & CMF_NEWFADE) - { - // Get the "best" color. - // Our brightest color's value, if we're fading to a darker color, - // or our (inverted) darkest color's value, if we're fading to a brighter color. - if (cbrightness < cdestbright) - { - cbest = 255.0l - min(r, min(g, b)); - cdist = 255.0l - max(cdestr, max(cdestg, cdestb)); - } - else - { - cbest = max(r, max(g, b)); - cdist = min(cdestr, min(cdestg, cdestb)); - } - - // Add/subtract this value during fading. - brightChange[i] = (fabs(cbest - cdist) / (double)fadedist) * fmaskamt; - } - else - { - deltas[i][0] = (map[i][0] - cdestr) / (double)fadedist; - deltas[i][1] = (map[i][1] - cdestg) / (double)fadedist; - deltas[i][2] = (map[i][2] - cdestb) / (double)fadedist; - } + deltas[i][2] = (map[i][2] - cdestb) / (double)fadedist; } // Now allocate memory for the actual colormap array itself! // aligned on 8 bit for asm code - colormap_p = Z_MallocAlign((COLORMAP_SIZE * (encoremap ? 2 : 1)) + 10, PU_LEVEL, NULL, 8); - lighttable = (UINT8 *)colormap_p; + colormap_p = (char *)extra_colormap->colormap; // Calculate the palette index for each palette index, for each light level // (as well as the two unused colormap lines we inherited from Doom) - for (p = 0; p < LIGHTLEVELS; p++) + for (p = 0; p < 34; p++) { for (i = 0; i < 256; i++) { - *colormap_p = NearestColor((UINT8)RoundUp(map[i][0]), - (UINT8)RoundUp(map[i][1]), - (UINT8)RoundUp(map[i][2])); + *colormap_p = NearestColorFunc((UINT8)M_RoundUp(map[i][0]), + (UINT8)M_RoundUp(map[i][1]), + (UINT8)M_RoundUp(map[i][2])); colormap_p++; if ((UINT32)p < fadestart) continue; - - if (extra_colormap->flags & CMF_NEWFADE) - { - // Add/subtract towards the destination color. - if (fabs(map[i][0] - cdestr) <= brightChange[i]) - map[i][0] = cdestr; - else if (map[i][0] > cdestr) - map[i][0] -= brightChange[i]; - else - map[i][0] += brightChange[i]; - - if (fabs(map[i][1] - cdestg) <= brightChange[i]) - map[i][1] = cdestg; - else if (map[i][1] > cdestg) - map[i][1] -= brightChange[i]; - else - map[i][1] += brightChange[i]; - - if (fabs(map[i][2] - cdestb) <= brightChange[i]) - map[i][2] = cdestb; - else if (map[i][2] > cdestb) - map[i][2] -= brightChange[i]; - else - map[i][2] += brightChange[i]; - } + #define ABS2(x) ((x) < 0 ? -(x) : (x)) + if (ABS2(map[i][0] - cdestr) > ABS2(deltas[i][0])) + map[i][0] -= deltas[i][0]; else - { - if (fabs(map[i][0] - cdestr) > fabs(deltas[i][0])) - map[i][0] -= deltas[i][0]; - else - map[i][0] = cdestr; + map[i][0] = cdestr; - if (fabs(map[i][1] - cdestg) > fabs(deltas[i][1])) - map[i][1] -= deltas[i][1]; - else - map[i][1] = cdestg; + if (ABS2(map[i][1] - cdestg) > ABS2(deltas[i][1])) + map[i][1] -= deltas[i][1]; + else + map[i][1] = cdestg; - if (fabs(map[i][2] - cdestb) > fabs(deltas[i][1])) // typo alert! - map[i][2] -= deltas[i][2]; - else - map[i][2] = cdestb; - } - } - } - - if (encoremap) - { - lighttable_t *colormap_p2 = lighttable; - - for (p = 0; p < LIGHTLEVELS; p++) - { - for (i = 0; i < 256; i++) - { - *colormap_p = colormap_p2[encoremap[i]]; - colormap_p++; - } - colormap_p2 += 256; + if (ABS2(map[i][2] - cdestb) > ABS2(deltas[i][1])) + map[i][2] -= deltas[i][2]; + else + map[i][2] = cdestb; + #undef ABS2 } } } - - return lighttable; } extracolormap_t *R_CreateColormapFromLinedef(char *p1, char *p2, char *p3) @@ -841,7 +801,7 @@ extracolormap_t *R_CreateColormapFromLinedef(char *p1, char *p2, char *p3) UINT8 cr = 0, cg = 0, cb = 0, ca = 0, cfr = 0, cfg = 0, cfb = 0, cfa = 25; UINT32 fadestart = 0, fadeend = 31; UINT8 flags = 0; - INT32 rgba = 0, fadergba = 0x19000000; + INT32 rgba = 0, fadergba = 0xFF000000; #define HEX2INT(x) (UINT32)(x >= '0' && x <= '9' ? x - '0' : x >= 'a' && x <= 'f' ? x - 'a' + 10 : x >= 'A' && x <= 'F' ? x - 'A' + 10 : 0) #define ALPHA2INT(x) (x >= 'a' && x <= 'z' ? x - 'a' : x >= 'A' && x <= 'Z' ? x - 'A' : x >= '0' && x <= '9' ? 25 : 0) @@ -849,13 +809,13 @@ extracolormap_t *R_CreateColormapFromLinedef(char *p1, char *p2, char *p3) // Get base colormap value // First alpha-only, then full value if (p1[0] >= 'a' && p1[0] <= 'z' && !p1[1]) - ca = (p1[0] - 'a'); + ca = ((p1[0] - 'a') * 102) / 10; else if (p1[0] == '#' && p1[1] >= 'a' && p1[1] <= 'z' && !p1[2]) - ca = (p1[1] - 'a'); + ca = ((p1[1] - 'a') * 102) / 10; else if (p1[0] >= 'A' && p1[0] <= 'Z' && !p1[1]) - ca = (p1[0] - 'A'); + ca = ((p1[0] - 'A') * 102) / 10; else if (p1[0] == '#' && p1[1] >= 'A' && p1[1] <= 'Z' && !p1[2]) - ca = (p1[1] - 'A'); + ca = ((p1[1] - 'A') * 102) / 10; else if (p1[0] == '#') { // For each subsequent value, the value before it must exist @@ -871,20 +831,20 @@ extracolormap_t *R_CreateColormapFromLinedef(char *p1, char *p2, char *p3) cb = ((HEX2INT(p1[5]) * 16) + HEX2INT(p1[6])); if (p1[7] >= 'a' && p1[7] <= 'z') - ca = (p1[7] - 'a'); + ca = ((p1[7] - 'a') * 102) / 10; else if (p1[7] >= 'A' && p1[7] <= 'Z') - ca = (p1[7] - 'A'); + ca = ((p1[7] - 'A') * 102) / 10; else - ca = 25; + ca = 255; } else - ca = 25; + ca = 255; } else - ca = 25; + ca = 255; } else - ca = 25; + ca = 255; } #define NUMFROMCHAR(c) (c >= '0' && c <= '9' ? c - '0' : 0) @@ -914,13 +874,13 @@ extracolormap_t *R_CreateColormapFromLinedef(char *p1, char *p2, char *p3) // Get fade (dark) colormap value // First alpha-only, then full value if (p3[0] >= 'a' && p3[0] <= 'z' && !p3[1]) - cfa = (p3[0] - 'a'); + cfa = ((p3[0] - 'a') * 102) / 10; else if (p3[0] == '#' && p3[1] >= 'a' && p3[1] <= 'z' && !p3[2]) - cfa = (p3[1] - 'a'); + cfa = ((p3[1] - 'a') * 102) / 10; else if (p3[0] >= 'A' && p3[0] <= 'Z' && !p3[1]) - cfa = (p3[0] - 'A'); + cfa = ((p3[0] - 'A') * 102) / 10; else if (p3[0] == '#' && p3[1] >= 'A' && p3[1] <= 'Z' && !p3[2]) - cfa = (p3[1] - 'A'); + cfa = ((p3[1] - 'A') * 102) / 10; else if (p3[0] == '#') { // For each subsequent value, the value before it must exist @@ -936,20 +896,20 @@ extracolormap_t *R_CreateColormapFromLinedef(char *p1, char *p2, char *p3) cfb = ((HEX2INT(p3[5]) * 16) + HEX2INT(p3[6])); if (p3[7] >= 'a' && p3[7] <= 'z') - cfa = (p3[7] - 'a'); + cfa = ((p3[7] - 'a') * 102) / 10; else if (p3[7] >= 'A' && p3[7] <= 'Z') - cfa = (p3[7] - 'A'); + cfa = ((p3[7] - 'A') * 102) / 10; else - cfa = 25; + cfa = 255; } else - cfa = 25; + cfa = 255; } else - cfa = 25; + cfa = 255; } else - cfa = 25; + cfa = 255; } #undef ALPHA2INT #undef HEX2INT @@ -1166,20 +1126,6 @@ UINT8 NearestPaletteColor(UINT8 r, UINT8 g, UINT8 b, RGBA_t *palette) return (UINT8)bestcolor; } -// Rounds off floating numbers and checks for 0 - 255 bounds -static int RoundUp(double number) -{ - if (number > 255.0l) - return 255; - if (number < 0.0l) - return 0; - - if ((int)number <= (int)(number - 0.5f)) - return (int)number + 1; - - return (int)number; -} - #ifdef EXTRACOLORMAPLUMPS const char *R_NameForColormap(extracolormap_t *extra_colormap) { diff --git a/src/r_data.h b/src/r_data.h index 0751ca1ee..03c1dc9a6 100644 --- a/src/r_data.h +++ b/src/r_data.h @@ -93,6 +93,7 @@ typedef enum TMCF_OVERRIDE = 1<<13, } textmapcolormapflags_t; +void R_GenerateLightTable(extracolormap_t *extra_colormap, boolean uselookup); lighttable_t *R_CreateLightTable(extracolormap_t *extra_colormap); extracolormap_t * R_CreateColormapFromLinedef(char *p1, char *p2, char *p3); extracolormap_t* R_CreateColormap(INT32 rgba, INT32 fadergba, UINT8 fadestart, UINT8 fadeend, UINT8 flags); From 334876535dc54ae45480cada988dc2dddc3ebf25 Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Sun, 8 Mar 2026 13:17:46 -0400 Subject: [PATCH 06/18] Lua colorlib --- src/lua_colorlib.c | 345 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 329 insertions(+), 16 deletions(-) diff --git a/src/lua_colorlib.c b/src/lua_colorlib.c index b60245f30..6e0f51c3b 100644 --- a/src/lua_colorlib.c +++ b/src/lua_colorlib.c @@ -13,11 +13,33 @@ #include "doomdef.h" #include "fastcmp.h" #include "r_data.h" +#include "v_video.h" +#include "k_color.h" #include "lua_script.h" #include "lua_libs.h" -#include "k_color.h" +#define COLORLIB_USE_LOOKUP + +#ifdef COLORLIB_USE_LOOKUP + static colorlookup_t colormix_lut; + #define GetNearestColor(r, g, b) GetColorLUT(&colormix_lut, r, g, b) +#else + #define GetNearestColor(r, g, b) NearestPaletteColor(r, g, b, pMasterPalette) +#endif + +//////////////// +// Color library +//////////////// + +static int lib_colorPaletteToRgb(lua_State *L) +{ + RGBA_t color = V_GetMasterColor((UINT8)luaL_checkinteger(L, 1)); + lua_pushinteger(L, color.s.red); + lua_pushinteger(L, color.s.green); + lua_pushinteger(L, color.s.blue); + return 3; +} #define IS_HEX_CHAR(x) ((x >= '0' && x <= '9') || (x >= 'a' && x <= 'f') || (x >= 'A' && x <= 'F')) #define ARE_HEX_CHARS(str, i) IS_HEX_CHAR(str[i]) && IS_HEX_CHAR(str[i + 1]) @@ -34,6 +56,13 @@ static UINT32 hex2int(char x) return 0; } +static UINT8 GetHTMLColorLength(const char *str) +{ + if (str[0] == '#') + str++; + return strlen(str) >= 8 ? 4 : 3; +} + static UINT8 ParseHTMLColor(const char *str, UINT8 *rgba, size_t numc) { const char *hex = str; @@ -79,6 +108,302 @@ static UINT8 ParseHTMLColor(const char *str, UINT8 *rgba, size_t numc) return 0; } +static UINT8 GetPackedRGBA(UINT32 colors, UINT8 *rgba) +{ + if (colors > 0xFFFFFF) + { + rgba[0] = (colors >> 24) & 0xFF; + rgba[1] = (colors >> 16) & 0xFF; + rgba[2] = (colors >> 8) & 0xFF; + rgba[3] = colors & 0xFF; + return 4; + } + else + { + rgba[0] = (colors >> 16) & 0xFF; + rgba[1] = (colors >> 8) & 0xFF; + rgba[2] = colors & 0xFF; + rgba[3] = 0xFF; + return 3; + } +} + +static UINT8 GetArgsRGBA(lua_State *L, UINT8 index, INT32 *r, INT32 *g, INT32 *b, INT32 *a) +{ + UINT8 rgba[4] = { 0, 0, 0, 255 }; + UINT8 num = 0; + + if (lua_gettop(L) == 1 && lua_type(L, index) == LUA_TNUMBER) + { + num = GetPackedRGBA(luaL_checkinteger(L, 1), rgba); + + *r = rgba[0]; + *g = rgba[1]; + *b = rgba[2]; + if (a) + *a = rgba[3]; + } + else if (lua_type(L, index) == LUA_TSTRING) + { + const char *str = lua_tostring(L, index); + UINT8 parsed = ParseHTMLColor(str, rgba, GetHTMLColorLength(str)); + if (!parsed) + luaL_error(L, "Malformed HTML color '%s'", str); + + num = parsed == 8 ? 4 : 3; + + *r = rgba[0]; + *g = rgba[1]; + *b = rgba[2]; + if (a) + *a = rgba[3]; + } + else + { + INT32 temp; + +#define CHECKINT(i) luaL_checkinteger(L, i) +#define GETCOLOR(c, i, desc) { \ + temp = CHECKINT(i); \ + if (temp < 0 || temp > 255) \ + luaL_error(L, desc " channel %d out of range (0 - 255)", temp); \ + c = temp; \ + num++; \ + } + + GETCOLOR(*r, index + 0, "red color"); + GETCOLOR(*g, index + 1, "green color"); + GETCOLOR(*b, index + 2, "blue color"); +#undef CHECKINT +#define CHECKINT(i) luaL_optinteger(L, i, 255) + if (a) + GETCOLOR(*a, index + 3, "alpha"); +#undef CHECKINT +#undef GETCOLOR + + num = 3 + (lua_type(L, index + 3) == LUA_TNUMBER); + } + + return num; +} + +static int lib_colorRgbToPalette(lua_State *L) +{ + INT32 r, g, b; + GetArgsRGBA(L, 1, &r, &g, &b, NULL); + +#ifdef COLORLIB_USE_LOOKUP + InitColorLUT(&colormix_lut, pMasterPalette, false); +#endif + + lua_pushinteger(L, GetNearestColor(r, g, b)); + return 1; +} + +#define SCALE_UINT8_TO_FIXED(val) FixedDiv(val * FRACUNIT, 255 * FRACUNIT) +#define SCALE_FIXED_TO_UINT8(val) FixedRound(FixedMul(val, 255 * FRACUNIT)) / FRACUNIT + +static fixed_t hue2rgb(fixed_t p, fixed_t q, fixed_t t) +{ + if (t < 0) + t += FRACUNIT; + if (t > FRACUNIT) + t -= FRACUNIT; + + fixed_t out; + + if (t < FRACUNIT / 6) + out = p + FixedMul(FixedMul(q - p, 6 * FRACUNIT), t); + else if (t < FRACUNIT / 2) + out = q; + else if (t < 2 * FRACUNIT / 3) + out = p + FixedMul(FixedMul(q - p, 2 * FRACUNIT / 3 - t), 6 * FRACUNIT); + else + out = p; + + return out; +} + +static int lib_colorHslToRgb(lua_State *L) +{ + fixed_t h, s, l; + +#define GETHSL(c, i, desc) \ + c = luaL_checkinteger(L, i); \ + if (c < 0 || c > 255) \ + luaL_error(L, desc " %d out of range (0 - 255)", c) + + GETHSL(h, 1, "hue"); + GETHSL(s, 2, "saturation"); + GETHSL(l, 3, "value"); +#undef GETHSL + + if (!s) + { + lua_pushinteger(L, l); + lua_pushinteger(L, l); + lua_pushinteger(L, l); + } + else + { + h = SCALE_UINT8_TO_FIXED(h); + s = SCALE_UINT8_TO_FIXED(s); + l = SCALE_UINT8_TO_FIXED(l); + + fixed_t q, p; + + if (l < FRACUNIT/2) + q = FixedMul(l, FRACUNIT + s); + else + q = l + s - FixedMul(l, s); + + p = l * 2 - q; + + lua_pushinteger(L, SCALE_FIXED_TO_UINT8(hue2rgb(p, q, h + FRACUNIT/3))); + lua_pushinteger(L, SCALE_FIXED_TO_UINT8(hue2rgb(p, q, h))); + lua_pushinteger(L, SCALE_FIXED_TO_UINT8(hue2rgb(p, q, h - FRACUNIT/3))); + } + + return 3; +} + +static int lib_colorRgbToHsl(lua_State *L) +{ + INT32 ir, ig, ib; + GetArgsRGBA(L, 1, &ir, &ig, &ib, NULL); + + fixed_t r = SCALE_UINT8_TO_FIXED(ir); + fixed_t g = SCALE_UINT8_TO_FIXED(ig); + fixed_t b = SCALE_UINT8_TO_FIXED(ib); + + fixed_t cmin = min(min(r, g), b); + fixed_t cmax = max(max(r, g), b); + + fixed_t h, s, l = (cmax + cmin) / 2; + fixed_t delta = cmax - cmin; + + if (!delta) + h = s = 0; + else + { + if (l > FRACUNIT / 2) + s = FixedDiv(delta, (FRACUNIT * 2) - cmax - cmin); + else + s = FixedDiv(delta, cmax + cmin); + + if (r > g && r > b) + { + h = FixedDiv(g - b, delta); + + if (g < b) + h += FRACUNIT * 6; + } + else + { + h = FixedDiv(r - g, delta); + + if (g > b) + h += FRACUNIT * 2; + else + h += FRACUNIT * 4; + } + + h = FixedDiv(h, FRACUNIT * 6); + } + + lua_pushinteger(L, SCALE_FIXED_TO_UINT8(h)); + lua_pushinteger(L, SCALE_FIXED_TO_UINT8(s)); + lua_pushinteger(L, SCALE_FIXED_TO_UINT8(l)); + + return 3; +} + +static int lib_colorHexToRgb(lua_State *L) +{ + UINT8 rgba[4] = { 0, 0, 0, 255 }; + + const char *str = luaL_checkstring(L, 1); + UINT8 parsed = ParseHTMLColor(str, rgba, 4), num = 3; + if (!parsed) + luaL_error(L, "Malformed HTML color '%s'", str); + else if (parsed == 8) + num++; + + lua_pushinteger(L, rgba[0]); + lua_pushinteger(L, rgba[1]); + lua_pushinteger(L, rgba[2]); + if (num == 4) + lua_pushinteger(L, rgba[3]); + + return num; +} + +static int lib_colorRgbToHex(lua_State *L) +{ + INT32 r, g, b, a; + UINT8 num = GetArgsRGBA(L, 1, &r, &g, &b, &a); + + char buffer[10]; + if (num >= 4) + snprintf(buffer, sizeof buffer, "#%02X%02X%02X%02X", r, g, b, a); + else + snprintf(buffer, sizeof buffer, "#%02X%02X%02X", r, g, b); + + lua_pushstring(L, buffer); + return 1; +} + +static int lib_colorPackRgb(lua_State *L) +{ + INT32 r, g, b; + + GetArgsRGBA(L, 1, &r, &g, &b, NULL); + + UINT32 packed = ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | (b & 0xFF); + + lua_pushinteger(L, packed); + return 1; +} + +static int lib_colorPackRgba(lua_State *L) +{ + INT32 r, g, b, a; + + GetArgsRGBA(L, 1, &r, &g, &b, &a); + + UINT32 packed = ((r & 0xFF) << 24) | ((g & 0xFF) << 16) | ((b & 0xFF) << 8) | (a & 0xFF); + + lua_pushinteger(L, packed); + return 1; +} + +static int lib_colorUnpackRgb(lua_State *L) +{ + UINT8 rgba[4]; + + UINT8 num = GetPackedRGBA(lua_tointeger(L, 1), rgba); + + for (UINT8 i = 0; i < num; i++) + { + lua_pushinteger(L, rgba[i]); + } + + return num; +} + +static luaL_Reg color_lib[] = { + {"paletteToRgb", lib_colorPaletteToRgb}, + {"rgbToPalette", lib_colorRgbToPalette}, + {"hslToRgb", lib_colorHslToRgb}, + {"rgbToHsl", lib_colorRgbToHsl}, + {"hexToRgb", lib_colorHexToRgb}, + {"rgbToHex", lib_colorRgbToHex}, + {"packRgb", lib_colorPackRgb}, + {"packRgba", lib_colorPackRgba}, + {"unpackRgb", lib_colorUnpackRgb}, + {NULL, NULL} +}; + ///////////////////////// // extracolormap userdata ///////////////////////// @@ -182,21 +507,7 @@ static void GetExtraColormapRGBA(lua_State *L, UINT8 *rgba, int arg) } else { - UINT32 colors = lua_tointeger(L, arg); - if (colors > 0xFFFFFF) - { - rgba[0] = (colors >> 24) & 0xFF; - rgba[1] = (colors >> 16) & 0xFF; - rgba[2] = (colors >> 8) & 0xFF; - rgba[3] = colors & 0xFF; - } - else - { - rgba[0] = (colors >> 16) & 0xFF; - rgba[1] = (colors >> 8) & 0xFF; - rgba[2] = colors & 0xFF; - rgba[3] = 0xFF; - } + GetPackedRGBA(lua_tointeger(L, arg), rgba); } } @@ -330,5 +641,7 @@ int LUA_ColorLib(lua_State *L) lua_setfield(L, -2, "__len"); lua_pop(L, 1); + luaL_register(L, "color", color_lib); + return 0; } From f62fb5361af1c98911ffa4cb44b41bf44d3d4261 Mon Sep 17 00:00:00 2001 From: NepDisk Date: Sun, 8 Mar 2026 13:41:24 -0400 Subject: [PATCH 07/18] Sorry no pickup for you lad --- src/r_main.cpp | 3 --- src/r_main.h | 1 - 2 files changed, 4 deletions(-) diff --git a/src/r_main.cpp b/src/r_main.cpp index bc31300ef..c5849d980 100644 --- a/src/r_main.cpp +++ b/src/r_main.cpp @@ -148,8 +148,6 @@ static CV_PossibleValue_t secbright_cons_t[] = {{0, "MIN"}, {255, "MAX"}, {0, NU static void Fov_OnChange(void); -consvar_t cv_tailspickup = CVAR_INIT ("tailspickup", "On", CV_NETVAR|CV_NOSHOWHELP, CV_OnOff, NULL); - // if enabled, load all graphics at level load consvar_t cv_precachetextures = CVAR_INIT ("precachetextures", "On", CV_SAVE, CV_OnOff, NULL); @@ -1797,7 +1795,6 @@ void R_RegisterEngineStuff(void) UINT8 i; CV_RegisterVar(&cv_gravity); - CV_RegisterVar(&cv_tailspickup); CV_RegisterVar(&cv_allowmlook); CV_RegisterVar(&cv_homremoval); diff --git a/src/r_main.h b/src/r_main.h index dbc0b89c6..d5af7669a 100644 --- a/src/r_main.h +++ b/src/r_main.h @@ -155,7 +155,6 @@ extern consvar_t cv_drawdist, cv_drawdist_precip; extern consvar_t cv_playerfade; extern consvar_t cv_fov[MAXSPLITSCREENPLAYERS]; extern consvar_t cv_skybox; -extern consvar_t cv_tailspickup; extern consvar_t cv_debugfinishline; extern consvar_t cv_secbright; From 9ca702dce8187f40089524d0538a4c2cc3187d87 Mon Sep 17 00:00:00 2001 From: NepDisk Date: Sun, 8 Mar 2026 14:02:28 -0400 Subject: [PATCH 08/18] Make consistency more accurate --- src/d_clisrv.c | 19 ++++++++++------- src/p_saveg.c | 58 ++++++++++++++++++++++++++++---------------------- src/p_saveg.h | 2 ++ 3 files changed, 45 insertions(+), 34 deletions(-) diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 327927239..245904258 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -5705,6 +5705,9 @@ static INT16 Consistancy(consistancy_t *c) mo = (mobj_t *)th; + if (!MobjIsNetSynced(mo)) + continue; + if (mo->flags & (MF_SPECIAL | MF_SOLID | MF_PUSHABLE | MF_BOSS | MF_MISSILE | MF_SPRING | MF_MONITOR | MF_FIRE | MF_ENEMY | MF_PAIN | MF_STICKY)) { c->mobjtype -= mo->type; @@ -5734,11 +5737,11 @@ static INT16 Consistancy(consistancy_t *c) c->mobjstate -= mo->target->state - states; c->mobjtics += mo->target->tics; c->mobjsprite -= mo->target->sprite; - c->mobjframe += mo->target->frame; + //c->mobjframe += mo->target->frame; } else ConsistancyXOR(c, 0x3333); - if (mo->tracer && mo->tracer->type != MT_OVERLAY) + if (mo->tracer && MobjIsNetSynced(mo->tracer)) { c->mobjtype += mo->tracer->type; c->position[0] -= mo->tracer->x; @@ -5754,12 +5757,12 @@ static INT16 Consistancy(consistancy_t *c) c->mobjstate -= mo->tracer->state - states; c->mobjtics += mo->tracer->tics; c->mobjsprite -= mo->tracer->sprite; - c->mobjframe += mo->tracer->frame; + //c->mobjframe += mo->tracer->frame; } else ConsistancyXOR(c, 0xAAAA); // SRB2Kart: We use hnext & hprev very extensively - if (mo->hnext && mo->hnext->type != MT_OVERLAY) + if (mo->hnext && MobjIsNetSynced(mo->hnext)) { c->mobjtype += mo->hnext->type; c->position[0] -= mo->hnext->x; @@ -5775,11 +5778,11 @@ static INT16 Consistancy(consistancy_t *c) c->mobjstate -= mo->hnext->state - states; c->mobjtics += mo->hnext->tics; c->mobjsprite -= mo->hnext->sprite; - c->mobjframe += mo->hnext->frame; + //c->mobjframe += mo->hnext->frame; } else ConsistancyXOR(c, 0x5555); - if (mo->hprev && mo->hprev->type != MT_OVERLAY) + if (mo->hprev && MobjIsNetSynced(mo->hprev)) { c->mobjtype += mo->hprev->type; c->position[0] -= mo->hprev->x; @@ -5795,14 +5798,14 @@ static INT16 Consistancy(consistancy_t *c) c->mobjstate -= mo->hprev->state - states; c->mobjtics += mo->hprev->tics; c->mobjsprite -= mo->hprev->sprite; - c->mobjframe += mo->hprev->frame; + //c->mobjframe += mo->hprev->frame; } else ConsistancyXOR(c, 0xCCCC); c->mobjstate -= mo->state - states; c->mobjtics += mo->tics; c->mobjsprite -= mo->sprite; - c->mobjframe += mo->frame; + //c->mobjframe += mo->frame; } } } diff --git a/src/p_saveg.c b/src/p_saveg.c index a6d8f6fa1..f67d299a3 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -2081,6 +2081,35 @@ static void DiffMobj(const mobj_t *mobj, UINT32 diff[]) DIFF(mobj->voice, MD3_VOICE); } +boolean MobjIsNetSynced(mobj_t *mobj) +{ + // Ignore stationary hoops - these will be respawned from mapthings. + if (mobj->type == MT_HOOP) + return false; + + // These are NEVER saved. + if (mobj->type == MT_HOOPCOLLIDE) + return false; + + // This hoop has already been collected. + if (mobj->type == MT_HOOPCENTER && mobj->threshold == 4242) + return false; + + // MT_SPARK: used for debug stuff + if (mobj->type == MT_SPARK) + return false; + + // MT_FOLLOWERHORN: So it turns out hornmod is fundamentally incompatible with netsync + if (mobj->type == MT_FOLLOWERHORN) + return false; + + // This is a non-synched visual effect mobj + if (mobj->flags2 & MF2_DONTSYNC) + return false; + + return true; +} + static thinker_t *SyncMobjThinker(savebuffer_t *save, actionf_p1 thinker, thinker_t *th, UINT8 type) { mobj_t *mobj = (mobj_t *)th; @@ -2089,24 +2118,7 @@ static thinker_t *SyncMobjThinker(savebuffer_t *save, actionf_p1 thinker, thinke if (save->write) { - // Ignore stationary hoops - these will be respawned from mapthings. - if (mobj->type == MT_HOOP) - return th; - - // These are NEVER saved. - if (mobj->type == MT_HOOPCOLLIDE) - return th; - - // This hoop has already been collected. - if (mobj->type == MT_HOOPCENTER && mobj->threshold == 4242) - return th; - - // MT_SPARK: used for debug stuff - if (mobj->type == MT_SPARK) - return th; - - // This is a non-synched visual effect mobj - if (mobj->flags2 & MF2_DONTSYNC) + if (!MobjIsNetSynced(mobj)) return th; // Scrap all of that. If we're a hoop center, this is ALL we're saving. @@ -3822,9 +3834,7 @@ static void P_RelinkPointers(savebuffer_t *save) mobj = (mobj_t *)currentthinker; - if (UNLIKELY(mobj->type == MT_HOOP || mobj->type == MT_HOOPCOLLIDE || mobj->type == MT_HOOPCENTER - // MT_SPARK: used for debug stuff - || mobj->type == MT_SPARK)) + if (!MobjIsNetSynced(mobj)) continue; RELINK(&mobj->tracer); @@ -4538,11 +4548,7 @@ void P_SaveNetGame(savebuffer_t *save, boolean resending) continue; mobj = (mobj_t *)th; - if (UNLIKELY(mobj->type == MT_HOOP || mobj->type == MT_HOOPCOLLIDE || mobj->type == MT_HOOPCENTER - // MT_SPARK: used for debug stuff - || mobj->type == MT_SPARK - // MT_FOLLOWERHORN: So it turns out hornmod is fundamentally incompatible with netsync - || mobj->type == MT_FOLLOWERHORN || mobj->flags2 & MF2_DONTSYNC)) + if (!MobjIsNetSynced(mobj)) continue; mobj->mobjnum = i++; } diff --git a/src/p_saveg.h b/src/p_saveg.h index a57578bc2..de0527dbe 100644 --- a/src/p_saveg.h +++ b/src/p_saveg.h @@ -61,6 +61,8 @@ boolean P_SaveBufferFromLump(savebuffer_t *save, lumpnum_t lump); size_t P_SaveBufferFromFile(savebuffer_t *save, char const *name); void P_SaveBufferFree(savebuffer_t *save); +boolean MobjIsNetSynced(mobj_t *mobj); + #ifdef __cplusplus } // extern "C" #endif From 794d59e52e1038c3c8c248eea04181b3a0c40313 Mon Sep 17 00:00:00 2001 From: Nicomedia <83q2520t@anonaddy.me> Date: Sun, 8 Mar 2026 19:24:29 +0100 Subject: [PATCH 09/18] Fix Discord SOC menus erroring out as Discord RPC defines disables the enums, making them impossible to get parsed. --- src/deh_soc.c | 11 +++++++++++ src/dehacked.c | 12 ++++++++++++ 2 files changed, 23 insertions(+) diff --git a/src/deh_soc.c b/src/deh_soc.c index c3b1ff5a2..1127229c3 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -2063,6 +2063,17 @@ static void readmenuitem(MYFILE *f, menuitem_t *menuitem) } else if (fastcmp(word, "SUBMENU")) { +#ifndef HAVE_DISCORDRPC + { + const char *testname = word2; + if (fastncmp("MN_", testname, 3)) + testname += 3; + if (fastcmp(testname, "OP_DISCORD") || fastcmp(testname, "MISC_DISCORDREQUESTS")) + { + continue; + } + } +#endif menutype_t mn = get_menutype(word2); if (mn == MAXMENUTYPES) { diff --git a/src/dehacked.c b/src/dehacked.c index 8a3c67656..ffbfcfb91 100644 --- a/src/dehacked.c +++ b/src/dehacked.c @@ -675,6 +675,18 @@ static void DEH_LoadDehackedFile(MYFILE *f, boolean mainfile) continue; // dedis don't need menus, silly! } +#ifndef HAVE_DISCORDRPC + { + const char *testname = word2; + if (fastncmp("MN_", testname, 3)) + testname += 3; + if (fastcmp(testname, "OP_DISCORD") || fastcmp(testname, "MISC_DISCORDREQUESTS")) + { + ignoremenulines(f); + continue; + } + } +#endif if (i == 0 && word2[0] != '0') // If word2 isn't a number i = get_menutype(word2); // find a huditem by name if (i >= 1 && i < nummenutypes) From 87122216c5924bd42a205caeaeb6f897c7318cfc Mon Sep 17 00:00:00 2001 From: NepDisk Date: Sun, 8 Mar 2026 18:04:19 -0400 Subject: [PATCH 10/18] Fix bouncy floors not killing airdrop and make generalized air drop kill function (and expose it to lua) --- src/k_kart.c | 13 +++++++++++++ src/k_kart.h | 2 ++ src/lua_baselib.c | 13 +++++++++++++ src/p_mobj.c | 7 +------ src/p_user.c | 7 +++++++ 5 files changed, 36 insertions(+), 6 deletions(-) diff --git a/src/k_kart.c b/src/k_kart.c index c9c418400..c96b6f17c 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -7155,6 +7155,19 @@ static void K_AirDrop(player_t *player, ticcmd_t *cmd) player->airdroppredelay++; } +void K_KillAirDrop(player_t *player, p_airdropflags_t airdropflags) +{ + if (!player) + return; + + if (player->airdropflags & airdropflags) + { + player->airdroptime = 0; + player->airdroppredelay = 0; + player->airdropflags &= ~PAF_AIRDROP_MASK; + } +} + // Returns the bumpspark value as an enum. INT32 K_GetBumpSpark(void) { diff --git a/src/k_kart.h b/src/k_kart.h index 6cfd73707..4cd6d4305 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -414,6 +414,8 @@ typedef enum AIRDROP_FUSION, } airdroptype_t; +void K_KillAirDrop(player_t *player, p_airdropflags_t airdropflags); + boolean K_NullDriftTiltEnabled(void); #define RECOVERYDASHADD (TICRATE/2) diff --git a/src/lua_baselib.c b/src/lua_baselib.c index 63f9c331f..4f43b76a7 100644 --- a/src/lua_baselib.c +++ b/src/lua_baselib.c @@ -4375,6 +4375,18 @@ static int lib_kSafeRespawnPositionEx(lua_State *L) return 1; } +static int lib_kKillAirDrop(lua_State *L) +{ + player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER)); + p_airdropflags_t airdropflags = luaL_checkinteger(L, 2); + + if (!player) + return LUA_ErrInvalid(L, "player_t"); + + K_KillAirDrop(player, airdropflags); + return 0; +} + // Checks if Rings are applicable. static int lib_kRingsActive(lua_State *L) { @@ -5770,6 +5782,7 @@ static luaL_Reg lib[] = { {"K_SetHyudoroCooldown",lib_kSetHyuCountdown}, {"K_SafeRespawnPosition",lib_kSafeRespawnPosition}, {"K_SafeRespawnPositionEx",lib_kSafeRespawnPositionEx}, + {"K_KillAirDrop", lib_kKillAirDrop}, {"K_GetCollideAngle",lib_kGetCollideAngle}, diff --git a/src/p_mobj.c b/src/p_mobj.c index d09acba2c..a7683796d 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -3406,12 +3406,7 @@ void P_MobjCheckWater(mobj_t *mobj) mobj->momz = FixedMul(mobj->momz, FixedDiv(2*FRACUNIT, 5*FRACUNIT)); // kill momentum significantly, to make the goo feel thick. // Kill heavy airdrop too so you don't get softlocked. - if (mobj->player && (mobj->player->airdropflags & PAF_AIRDROP_HEAVY)) - { - mobj->player->airdroptime = 0; - mobj->player->airdroppredelay = 0; - mobj->player->airdropflags &= ~PAF_AIRDROP_MASK; - } + K_KillAirDrop(mobj->player, PAF_AIRDROP_HEAVY); } else if (wasinwater && P_MobjFlip(mobj) * mobj->momz > 0) { diff --git a/src/p_user.c b/src/p_user.c index b12c21904..d5ced7f91 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -1692,6 +1692,9 @@ static void P_CheckBouncySectors(player_t *player) { player->mo->momx = -FixedMul(player->mo->momx,rover->bouncestrength); player->mo->momy = -FixedMul(player->mo->momy,rover->bouncestrength); + + // Kill heavy airdrop too so you don't get softlocked. + K_KillAirDrop(player, PAF_AIRDROP_HEAVY); } else { @@ -1731,6 +1734,10 @@ static void P_CheckBouncySectors(player_t *player) player->mo->momx = momentum.x; player->mo->momy = momentum.y; player->mo->momz = momentum.z; + + // Kill heavy airdrop too so you don't get softlocked. + K_KillAirDrop(player, PAF_AIRDROP_HEAVY); + } goto bouncydone; } From edcb7dbe69fc2f1992de4273cb70a35e27d27e00 Mon Sep 17 00:00:00 2001 From: NepDisk Date: Sun, 8 Mar 2026 18:15:09 -0400 Subject: [PATCH 11/18] Disallow picking up SPB rings in first --- src/p_enemy.c | 3 +++ src/p_inter.c | 6 +++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/p_enemy.c b/src/p_enemy.c index 061d5526a..e3ba64846 100644 --- a/src/p_enemy.c +++ b/src/p_enemy.c @@ -11066,6 +11066,9 @@ void A_SPBChase(void *thing) specialring->colorized = true; specialring->color = SKINCOLOR_RED; specialring->flags |= MF_NOCLIPHEIGHT; + + // But don't let first pick these up. + specialring->extravalue3 = 1; } } diff --git a/src/p_inter.c b/src/p_inter.c index 547619eff..c0da4b857 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -629,7 +629,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) if (special->extravalue1) return; - // No picking up rings while SPB is targetting you + // No picking up rings while your rings are locked. if (player->pflags & PF_RINGLOCK) return; @@ -637,6 +637,10 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) if (special->threshold > 0 || P_PlayerInPain(player)) return; + // Don't pick up SPB rings in first. + if (special->extravalue3 && player->position == 1) + return; + if (!(P_CanPickupItem(player, PICKUPITEM_RING))) return; From 86df6c78c6c9893b30aec0053845d4e2a640a763 Mon Sep 17 00:00:00 2001 From: NepDisk Date: Sun, 8 Mar 2026 18:19:41 -0400 Subject: [PATCH 12/18] Add nights bumper specfic check --- src/p_user.c | 6 +++++- src/r_patchrotation.c | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/p_user.c b/src/p_user.c index d5ced7f91..e74ae7b92 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -3892,7 +3892,11 @@ static angle_t P_GetCameraPitchRollAngle(mobj_t *mobj, player_t *viewPlayer) else { // For regular objects, use the camera; just not the *player's* camera. - viewingAngle = R_PointToAngleFloat(mobj->x, mobj->y); + //... Unless you are a Nights bumber... + if (mobj->type == MT_NIGHTSBUMPER) + viewingAngle = mobj->angle; + else + viewingAngle = R_PointToAngleFloat(mobj->x, mobj->y); } pitchMul = -FINESINE(viewingAngle >> ANGLETOFINESHIFT); diff --git a/src/r_patchrotation.c b/src/r_patchrotation.c index 251cfe141..8d7b08355 100644 --- a/src/r_patchrotation.c +++ b/src/r_patchrotation.c @@ -39,7 +39,11 @@ angle_t R_GetPitchRollAngle(mobj_t *mobj, player_t *viewPlayer, interpmobjstate_ else { // For regular objects, use the camera; just not the *player's* camera. - viewingAngle = R_PointToAngleFloat(mobj->x, mobj->y); + //... Unless you are a Nights bumber... + if (mobj->type == MT_NIGHTSBUMPER) + viewingAngle = mobj->angle; + else + viewingAngle = R_PointToAngleFloat(mobj->x, mobj->y); } pitchMul = -FINESINE(viewingAngle >> ANGLETOFINESHIFT); From 3f0e421085377179f19f9eb000419834d905ed27 Mon Sep 17 00:00:00 2001 From: Alug Date: Mon, 9 Mar 2026 16:28:33 +0100 Subject: [PATCH 13/18] fix distorted/broken textures when switching from software to opengl if the map was loaded in software renderer mode the segs floatlength was not set since this only gets done on map load for non adjusted segs it broke texture mapping on non adjusted segs after switching to gl --- src/p_setup.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/p_setup.c b/src/p_setup.c index a06ea14db..1465e718f 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -3873,7 +3873,7 @@ static inline float P_SegLengthFloat(seg_t *seg) dx = FIXED_TO_FLOAT(seg->v2->x - seg->v1->x); dy = FIXED_TO_FLOAT(seg->v2->y - seg->v1->y); - return (float)hypot(dx, dy); + return hypotf(dx, dy); } #endif @@ -4033,7 +4033,7 @@ static void P_LoadSegs(UINT8 *data) seg->length = P_SegLength(seg); #ifdef HWRENDER - seg->flength = (rendermode == render_opengl) ? P_SegLengthFloat(seg) : 0; + seg->flength = P_SegLengthFloat(seg); #endif seg->glseg = false; From b7ade320201be1e5c42ba611cdc271938ec02e74 Mon Sep 17 00:00:00 2001 From: Alug Date: Mon, 9 Mar 2026 18:15:38 +0100 Subject: [PATCH 14/18] port and update the opengl map preprocessor from Doom-Legacy this fixes numerous issues with holes in map geometry, misplaced sectors/planes, rare infinite loop hangs most notably fixes most rendering issues on UDMF/RR maps needs some testing as this may be slower than the old one --- src/hardware/hw_bsp.c | 22 +- src/hardware/hw_drawnodes.c | 4 +- src/hardware/hw_glob.h | 14 +- src/hardware/hw_main.c | 16 +- src/hardware/hw_main.h | 4 +- src/hardware/hw_map.c | 3327 +++++++++++++++++++++++++++-------- src/hardware/hw_plane.c | 2 +- src/p_setup.c | 15 +- 8 files changed, 2682 insertions(+), 722 deletions(-) diff --git a/src/hardware/hw_bsp.c b/src/hardware/hw_bsp.c index 8d2e40431..e0a3572b3 100644 --- a/src/hardware/hw_bsp.c +++ b/src/hardware/hw_bsp.c @@ -478,7 +478,7 @@ static void HWR_AddPolyObjectPlanes(void) sector_t *polyobjsector; INT32 light = 0; - // Polyobject Planes need their own function for drawing because they don't have extrasubsectors by themselves + // Polyobject Planes need their own function for drawing because they don't have poly_subsectors by themselves // It should be okay because polyobjects should always be convex anyway for (i = 0; i < numpolys; i++) @@ -567,9 +567,9 @@ static void HWR_Subsector(size_t num) // CONS_Printf("subsector %d in portal\n", (INT32)num); #ifdef PARANOIA //no risk while developing, enough debugging nights! - if (num >= addsubsector) + if (num >= num_poly_subsector) I_Error("HWR_Subsector: ss %s with numss = %s, addss = %s\n", - sizeu1(num), sizeu2(numsubsectors), sizeu3(addsubsector)); + sizeu1(num), sizeu2(numsubsectors), sizeu3(num_poly_subsector)); /*if (num >= numsubsectors) I_Error("HWR_Subsector: ss %i with numss = %i", @@ -668,7 +668,7 @@ static void HWR_Subsector(size_t num) if (sub->validcount != validcount) { HWR_GetLevelFlat(&levelflats[gl_frontsector->floorpic], R_NoEncore(gl_frontsector, &levelflats[gl_frontsector->floorpic], false)); - HWR_RenderPlane(sub, &extrasubsectors[num], false, + HWR_RenderPlane(sub, &poly_subsectors[num], false, // Hack to make things continue to work around slopes. locFloorHeight == cullFloorHeight ? locFloorHeight : gl_frontsector->floorheight, // We now return you to your regularly scheduled rendering. @@ -685,7 +685,7 @@ static void HWR_Subsector(size_t num) if (sub->validcount != validcount) { HWR_GetLevelFlat(&levelflats[gl_frontsector->ceilingpic], R_NoEncore(gl_frontsector, &levelflats[gl_frontsector->ceilingpic], true)); - HWR_RenderPlane(sub, &extrasubsectors[num], true, + HWR_RenderPlane(sub, &poly_subsectors[num], true, // Hack to make things continue to work around slopes. locCeilingHeight == cullCeilingHeight ? locCeilingHeight : gl_frontsector->ceilingheight, // We now return you to your regularly scheduled rendering. @@ -725,7 +725,7 @@ static void HWR_Subsector(size_t num) alpha = HWR_FogBlockAlpha(*gl_frontsector->lightlist[light].lightlevel, rover->master->frontsector->extra_colormap); HWR_AddTransparentFloor(0, - &extrasubsectors[num], + &poly_subsectors[num], false, *rover->bottomheight, *gl_frontsector->lightlist[light].lightlevel, @@ -740,7 +740,7 @@ static void HWR_Subsector(size_t num) light = R_GetPlaneLight(gl_frontsector, centerHeight, viewz < cullHeight ? true : false); HWR_AddTransparentFloor(&levelflats[*rover->bottompic], - &extrasubsectors[num], + &poly_subsectors[num], false, *rover->bottomheight, *gl_frontsector->lightlist[light].lightlevel, @@ -751,7 +751,7 @@ static void HWR_Subsector(size_t num) { HWR_GetLevelFlat(&levelflats[*rover->bottompic], R_NoEncore(gl_frontsector, &levelflats[*rover->bottompic], false)); light = R_GetPlaneLight(gl_frontsector, centerHeight, viewz < cullHeight ? true : false); - HWR_RenderPlane(sub, &extrasubsectors[num], false, *rover->bottomheight, HWR_RippleBlend(gl_frontsector, rover, false) | PF_Occlude, *gl_frontsector->lightlist[light].lightlevel, &levelflats[*rover->bottompic], + HWR_RenderPlane(sub, &poly_subsectors[num], false, *rover->bottomheight, HWR_RippleBlend(gl_frontsector, rover, false) | PF_Occlude, *gl_frontsector->lightlist[light].lightlevel, &levelflats[*rover->bottompic], rover->master->frontsector, 255, *gl_frontsector->lightlist[light].extra_colormap); } } @@ -773,7 +773,7 @@ static void HWR_Subsector(size_t num) alpha = HWR_FogBlockAlpha(*gl_frontsector->lightlist[light].lightlevel, rover->master->frontsector->extra_colormap); HWR_AddTransparentFloor(0, - &extrasubsectors[num], + &poly_subsectors[num], true, *rover->topheight, *gl_frontsector->lightlist[light].lightlevel, @@ -788,7 +788,7 @@ static void HWR_Subsector(size_t num) light = R_GetPlaneLight(gl_frontsector, centerHeight, viewz < cullHeight ? true : false); HWR_AddTransparentFloor(&levelflats[*rover->toppic], - &extrasubsectors[num], + &poly_subsectors[num], true, *rover->topheight, *gl_frontsector->lightlist[light].lightlevel, @@ -799,7 +799,7 @@ static void HWR_Subsector(size_t num) { HWR_GetLevelFlat(&levelflats[*rover->toppic], R_NoEncore(gl_frontsector, &levelflats[*rover->toppic], true)); light = R_GetPlaneLight(gl_frontsector, centerHeight, viewz < cullHeight ? true : false); - HWR_RenderPlane(sub, &extrasubsectors[num], true, *rover->topheight, HWR_RippleBlend(gl_frontsector, rover, true) | PF_Occlude, *gl_frontsector->lightlist[light].lightlevel, &levelflats[*rover->toppic], + HWR_RenderPlane(sub, &poly_subsectors[num], true, *rover->topheight, HWR_RippleBlend(gl_frontsector, rover, true) | PF_Occlude, *gl_frontsector->lightlist[light].lightlevel, &levelflats[*rover->toppic], rover->master->frontsector, 255, *gl_frontsector->lightlist[light].extra_colormap); } } diff --git a/src/hardware/hw_drawnodes.c b/src/hardware/hw_drawnodes.c index f0030aeef..f48eec096 100644 --- a/src/hardware/hw_drawnodes.c +++ b/src/hardware/hw_drawnodes.c @@ -37,7 +37,7 @@ typedef struct typedef struct { - extrasubsector_t *xsub; + poly_subsector_t *xsub; boolean isceiling; fixed_t fixedheight; INT32 lightlevel; @@ -143,7 +143,7 @@ void HWR_AddTransparentWall(FOutVector *wallVerts, FSurfaceInfo *pSurf, INT32 te } // This will likely turn into a copy of HWR_Add3DWater and replace it. -void HWR_AddTransparentFloor(levelflat_t *levelflat, extrasubsector_t *xsub, boolean isceiling, fixed_t fixedheight, INT32 lightlevel, INT32 alpha, sector_t *FOFSector, FBITFIELD blend, boolean fogplane, extracolormap_t *planecolormap) +void HWR_AddTransparentFloor(levelflat_t *levelflat, poly_subsector_t *xsub, boolean isceiling, fixed_t fixedheight, INT32 lightlevel, INT32 alpha, sector_t *FOFSector, FBITFIELD blend, boolean fogplane, extracolormap_t *planecolormap) { planeinfo_t *planeinfo = HWR_CreateDrawNode(DRAWNODE_PLANE); diff --git a/src/hardware/hw_glob.h b/src/hardware/hw_glob.h index af954d486..1ec637413 100644 --- a/src/hardware/hw_glob.h +++ b/src/hardware/hw_glob.h @@ -54,7 +54,7 @@ typedef struct typedef struct { poly_t *planepoly; // the generated convex polygon -} extrasubsector_t; +} poly_subsector_t; // needed for sprite rendering // equivalent of the software renderer's vissprites @@ -117,16 +117,14 @@ typedef struct gl_vissprite_s void HWR_ObjectLightLevelPost(gl_vissprite_t *spr, const sector_t *sector, INT32 *lightlevel, boolean model); // -------- -// hw_bsp.c +// hw_map.c // -------- -extern extrasubsector_t *extrasubsectors; -extern size_t addsubsector; +extern poly_subsector_t *poly_subsectors; +extern size_t num_poly_subsector; void HWR_InitPolyPool(void); void HWR_FreePolyPool(void); -void HWR_FreeExtraSubsectors(void); - // -------- // hw_cache.c // -------- @@ -176,7 +174,7 @@ extern INT32 textureformat; // -------- // hw_plane.c // -------- -void HWR_RenderPlane(subsector_t *subsector, extrasubsector_t *xsub, boolean isceiling, fixed_t fixedheight, FBITFIELD PolyFlags, INT32 lightlevel, levelflat_t *levelflat, sector_t *FOFsector, UINT8 alpha, extracolormap_t *planecolormap); +void HWR_RenderPlane(subsector_t *subsector, poly_subsector_t *xsub, boolean isceiling, fixed_t fixedheight, FBITFIELD PolyFlags, INT32 lightlevel, levelflat_t *levelflat, sector_t *FOFsector, UINT8 alpha, extracolormap_t *planecolormap); void HWR_RenderPolyObjectPlane(polyobj_t *polysector, boolean isceiling, fixed_t fixedheight, FBITFIELD blendmode, UINT8 lightlevel, levelflat_t *levelflat, sector_t *FOFsector, UINT8 alpha, extracolormap_t *planecolormap); @@ -185,7 +183,7 @@ void HWR_RenderPolyObjectPlane(polyobj_t *polysector, boolean isceiling, fixed_t // hw_drawnodes.c // -------- void HWR_AddTransparentWall(FOutVector *wallVerts, FSurfaceInfo *pSurf, INT32 texnum, boolean noencore, FBITFIELD blend, boolean fogwall, INT32 lightlevel, extracolormap_t *wallcolormap); -void HWR_AddTransparentFloor(levelflat_t *levelflat, extrasubsector_t *xsub, boolean isceiling, fixed_t fixedheight, INT32 lightlevel, INT32 alpha, sector_t *FOFSector, FBITFIELD blend, boolean fogplane, extracolormap_t *planecolormap); +void HWR_AddTransparentFloor(levelflat_t *levelflat, poly_subsector_t *xsub, boolean isceiling, fixed_t fixedheight, INT32 lightlevel, INT32 alpha, sector_t *FOFSector, FBITFIELD blend, boolean fogplane, extracolormap_t *planecolormap); void HWR_AddTransparentPolyobjectFloor(levelflat_t *levelflat, polyobj_t *polysector, boolean isceiling, fixed_t fixedheight, INT32 lightlevel, INT32 alpha, sector_t *FOFSector, FBITFIELD blend, extracolormap_t *planecolormap); void HWR_PushDrawNodeState(void); diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c index e45f36ffb..71560d3a9 100644 --- a/src/hardware/hw_main.c +++ b/src/hardware/hw_main.c @@ -1255,6 +1255,17 @@ consvar_t cv_glanisotropicmode = CVAR_INIT ("gr_anisotropicmode", "1", CV_SAVE|C consvar_t cv_glsolvetjoin = CVAR_INIT ("gr_solvetjoin", "On", 0, CV_OnOff, NULL); +consvar_t cv_glpolytile = CVAR_INIT ("gr_polytile", "On", 0, CV_OnOff, NULL); + +CV_PossibleValue_t grpolyshape_cons_t[] = { + {0, "Subsector"}, + {1, "Fat"}, + {2, "Trim"}, + {3, "NotConvex"}, + {0, NULL} +}; +consvar_t cv_glpolyshape = CVAR_INIT ("gr_polygon_shape", "Trim", CV_SAVE, grpolyshape_cons_t, NULL); + consvar_t cv_glbatching = CVAR_INIT ("gr_batching", "On", 0, CV_OnOff, NULL); CV_PossibleValue_t glpalettedepth_cons_t[] = {{16, "16 bits"}, {24, "24 bits"}, {0, NULL}}; @@ -1365,7 +1376,10 @@ void HWR_AddCommands(void) CV_RegisterVar(&cv_glallowshaders); CV_RegisterVar(&cv_glfiltermode); + CV_RegisterVar(&cv_glsolvetjoin); + CV_RegisterVar(&cv_glpolytile); + CV_RegisterVar(&cv_glpolyshape); CV_RegisterVar(&cv_glbatching); @@ -1399,6 +1413,7 @@ void HWR_Startup(void) textureformat = patchformat = GL_TEXFMT_RGBA; HWR_InitPolyPool(); + HWR_AddSessionCommands(); HWR_InitMapTextures(); HWR_InitModels(); @@ -1445,7 +1460,6 @@ void HWR_Switch(void) void HWR_Shutdown(void) { CONS_Printf("HWR_Shutdown()\n"); - HWR_FreeExtraSubsectors(); HWR_FreePolyPool(); HWR_FreeMapTextures(); GL_FlushScreenTextures(); diff --git a/src/hardware/hw_main.h b/src/hardware/hw_main.h index 81e69fbfe..879f3c40b 100644 --- a/src/hardware/hw_main.h +++ b/src/hardware/hw_main.h @@ -110,11 +110,13 @@ extern consvar_t cv_glmodellighting; extern consvar_t cv_glfiltermode; extern consvar_t cv_glanisotropicmode; extern consvar_t cv_fovchange; -extern consvar_t cv_glsolvetjoin; + extern consvar_t cv_glshearing; extern consvar_t cv_glspritebillboarding; extern consvar_t cv_glskydome; +extern consvar_t cv_glsolvetjoin, cv_glpolytile, cv_glpolyshape; + extern consvar_t cv_glbatching; extern consvar_t cv_glpaletterendering; extern consvar_t cv_glpalettedepth; diff --git a/src/hardware/hw_map.c b/src/hardware/hw_map.c index ddfb262d1..ca6051b65 100644 --- a/src/hardware/hw_map.c +++ b/src/hardware/hw_map.c @@ -1,39 +1,627 @@ // BLANKART //----------------------------------------------------------------------------- -// Copyright (C) 1998-2000 by DooM Legacy Team. +// Copyright (C) 1993-1996 by id Software, Inc. +// Copyright (C) 1998-2016 by DooM Legacy Team. +// Copyright (C) 1999-2019 by Sonic Team Junior. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. // See the 'LICENSE' file for more details. //----------------------------------------------------------------------------- -/// \file hw_bsp.c +/// \file hw_map.c /// \brief convert SRB2 map -#include "../doomdef.h" -#include "../doomstat.h" #ifdef HWRENDER + +#include + #include "hw_glob.h" +#include "hw_main.h" #include "../r_local.h" #include "../z_zone.h" #include "../console.h" #include "../v_video.h" #include "../m_menu.h" #include "../i_system.h" -#include "../m_argv.h" #include "../i_video.h" -#include "../w_wad.h" -#include "../p_setup.h" // levelfadecol + +#define FIXED_TO_FLOAT_MULT (1.0f / 65536.0f) + +//#define DEBUG_HWBSP + +//#define DEBUG_TRACE +#ifdef DEBUG_TRACE +static int trigger_bsp_sector = 0xFFFFFFFF; +static int trigger_subsector = 0xFFFFFFFF; + // 0xFFFFFFF2 to trace all subsectors +static byte trigger_trace = 0; +#endif + +// Allocate poly from ZAlloc. +#define ZPLANALLOC + +#define POLYTILE + // -------------------------------------------------------------------------- // This is global data for planes rendering // -------------------------------------------------------------------------- -extrasubsector_t *extrasubsectors = NULL; +// ---- Polygon vertexes +// Separate allocations for level map vertexes, and BSP vertexes. -// newsubsectors are subsectors without segs, added for the plane polygons -#define NEWSUBSECTORS 50 -static size_t totsubsectors; -size_t addsubsector; +// Floating poly for level map vertexes. +// Can be indexed by level map vertex number. +polyvertex_t* poly_vert = NULL; + +// Create float poly vert from level map vertexes. +// These are freed by Z_Free( PU_HWRPLANE ). +static void create_poly_vert(void) +{ + polyvertex_t * pv; + size_t size = sizeof(polyvertex_t) * numvertexes; + size_t i; + + poly_vert = Z_Malloc(size, PU_HWRPLANE, NULL); + pv = &poly_vert[0]; + + for (i = 0; i < numvertexes; i++) + { + pv->x = FIXED_TO_FLOAT(vertexes[i].x); + pv->y = FIXED_TO_FLOAT(vertexes[i].y); + pv++; + } +} + +// Return true if p1 is a level map polyvertex in the poly_vert structure. +static inline boolean in_poly_vert(polyvertex_t * p1) +{ + return((p1 >= poly_vert) && (p1 < &poly_vert[numvertexes])); +} + +#define POLYSTORE_NUM_VERT 256 +typedef struct polyvertex_store_s { + struct polyvertex_store_s *next; // linked list + INT32 num_vert_used; + polyvertex_t pv[POLYSTORE_NUM_VERT]; +} polyvertex_store_t; + +// Extra poly vertex for BSP splits, segs, and divlines. +polyvertex_store_t *polyvert_store = NULL; + +// --- Same vertex + +// If two vertex coords have a x and y difference of less than 1 FRACUNIT, +// they could be considered the same point. +// Note: hardcoded value, 1.0f could be anything else. +//#define SAME_DIST 1.5f +// Dist 0.4999 cures HOM in Freedoom map09 +#define SAME_DIST 0.4999f +// ep : the max difference in x or y. +static inline boolean SameVertex(polyvertex_t* p1, polyvertex_t* p2, float ep) +{ + if (fabsf(p2->x - p1->x) > ep) + return false; + if (fabsf(p2->y - p1->y) > ep) + return false; + // p1 and p2 are considered the same vertex + return true; +} + +// Get a polyvertex from the BSP polyvertex store. +// These are freed by Z_Free( PU_HWRPLANE ). +static polyvertex_t *new_polyvertex(void) +{ + polyvertex_store_t *psp = polyvert_store; + + if (!polyvert_store + || (polyvert_store->num_vert_used >= POLYSTORE_NUM_VERT)) + { + // Need another storage unit. + polyvert_store = Z_Malloc(sizeof(polyvertex_store_t), PU_HWRPLANE, NULL); + polyvert_store->next = psp; // link for search + polyvert_store->num_vert_used = 0; + } + + // Return the next polyvertex in the storage unit. + return &polyvert_store->pv[polyvert_store->num_vert_used++]; +} + +// Search both the vertex lists for a close vertex. +// ep: how close they must be to be the same ( 0.001 to 1.5 ) +static polyvertex_t *find_close_polyvertex(float x, float y, float ep) +{ + polyvertex_store_t *psv; + polyvertex_t *pv; + INT32 i; + + // Search level map vertexes. + pv = poly_vert; + for (i = numvertexes; i > 0; i--) + { + const float dx = pv->x - x; + const float dy = pv->y - y; + if (dx*dx + dy*dy < ep*ep) + return pv; // close enough to be the same vertex + pv++; + } + + // Search extra BSP vertexes. + psv = polyvert_store; + while (psv) + { + // Search all vertex in a polyvertex_store_t + pv = psv->pv; + for (i = psv->num_vert_used; i > 0; i--) + { + const float dx = pv->x - x; + const float dy = pv->y - y; + if (dx*dx + dy*dy < ep*ep) + return pv; // close enough to be the same vertex + pv++; + } + psv = psv->next; + } + + return NULL; // none found +} + +// Store a new polyvertex. +// Search for an existing vertex that is within ep. +// Otherwise make a new extra vertex. +// ep: how close an existing vertex must be to be the same ( 0.001 to 1.5 ) +static inline polyvertex_t *store_polyvertex(polyvertex_t *vert, float ep) +{ + const float fx = vert->x; + const float fy = vert->y; + + polyvertex_t *vp = find_close_polyvertex(fx, fy, ep); + + if (!vp) + { + vp = new_polyvertex(); // new BSP polyvertex + vp->x = fx; + vp->y = fy; + } + + return vp; +} + +// Create a polyvertex for the vertex. +// v1 : fixed point vertex +// ep: how close an existing vertex must be to be the same ( 0.001 to 1.5 ) +static inline polyvertex_t *store_vertex(vertex_t *v1, float ep) +{ + const float fx = FIXED_TO_FLOAT(v1->x); + const float fy = FIXED_TO_FLOAT(v1->y); + polyvertex_t * vp = find_close_polyvertex(fx, fy, ep); + + if (!vp) + { + vp = new_polyvertex(); // new BSP polyvertex + vp->x = fx; + vp->y = fy; + } + + return vp; +} + +// ---- Working polygons +// Have ptr to vertex instead of copy, to make handling same vertex easier. +// Polygons are stored in clockwise vertex order. + +// Working poly +// A convex 'plane' polygon, clockwise order +typedef struct { + INT32 num_alloc, numpts; // allocation size and how many used + polyvertex_t **ppts; // ptr to array of ptrs + // Allocate with Z_Malloc, PU_HWRPLANE +#ifdef DEBUG_TRACE + UINT32 id1, id2, id3; // Tracking poly history +#endif +} wpoly_t; + +#ifdef DEBUG_TRACE +UINT32 poly_id = 1; +#endif + +#ifdef DEBUG_HWBSP +static void polyvertex_dump(polyvertex_t *pv) +{ + int j; + fixed_t x1 = pv->x * FRACUNIT; + fixed_t y1 = pv->y * FRACUNIT; + for (j = 0; j < numvertexes; j++) + { + vertex_t * vt = &vertexes[j]; + if (abs(x1 - vt->x) + abs(y1 - vt->y) < 2 ) + { + CONS_Debug(DBG_RENDER, " V%i(%6.2f, %6.2f)", j, pv->x, pv->y); + return; + } + } + + CONS_Debug(DBG_RENDER, " (%6.2f, %6.2f)", pv->x, pv->y); +} + +static void wpoly_dump(const char *str, wpoly_t *poly) +{ + int i, cnt = 0; + + cnt = strlen( str); +#ifdef DEBUG_TRACE + CONS_Debug(DBG_RENDER, " %s id=%i,%i,%i: ", str, poly->id1, poly->id2, poly->id3); + cnt += 23; +#else + CONS_Debug(DBG_RENDER, " %s: ", str); +#endif + for (i = 0; inumpts; i++) + { + if (cnt > 120 ) + { + cnt = 6; + CONS_Debug(DBG_RENDER, "\n "); + } + polyvertex_dump( poly->ppts[i]); + cnt+=20; + } + CONS_Debug(DBG_RENDER, "\n"); +} +#endif + +// Most basic initialize. +static void wpoly_init_0(wpoly_t * wpoly) +{ + wpoly->numpts = 0; + wpoly->num_alloc = 0; + wpoly->ppts = NULL; +} + +// Initialize at an initial size. +// num_alloc : num vertex, greater than 0 +static void wpoly_init_alloc(INT32 num_alloc, wpoly_t * wpoly) +{ + wpoly->numpts = 0; + wpoly->num_alloc = num_alloc; + wpoly->ppts = Z_Malloc((sizeof(void*) * num_alloc), PU_HWRPLANE, NULL); +} + +// Frees the allocation used by the wpoly. +static void wpoly_free(wpoly_t * wpoly) +{ + wpoly->num_alloc = 0; + wpoly->numpts = 0; + + if (wpoly->ppts) + { + Z_Free(wpoly->ppts); + wpoly->ppts = NULL; + } +} + +// Move all vertex from one poly to another. +// from_poly : source, is left empty +// to_poly : previous content is lost +static void wpoly_move(wpoly_t * from_poly, /*OUT*/ wpoly_t * to_poly) +{ + if (to_poly->ppts) + wpoly_free(to_poly); + + *to_poly = *from_poly; // copy ptrs and sizes + // Content moved, cannot free. + from_poly->ppts = NULL; + from_poly->num_alloc = 0; + from_poly->numpts = 0; + // from_poly is empty. +} + +// Append a range from one poly to another poly. +// Does not alloc more, will limit copy to allocation size. +static void wpoly_append(wpoly_t *src_poly, INT32 copy_from, INT32 copy_cnt, /*OUT*/ wpoly_t *dest_poly) +{ + polyvertex_t ** pvp; + INT32 n; + +#ifdef DEBUG_HWBSP + if (copy_cnt > src_poly->numpts ) + { + CONS_Debug(DBG_RENDER, "ERROR wpoly_append, exceeds src bounds, copy_from= %i, copy_cnt= %i, src numpts= %i\n", + copy_cnt, src_poly->numpts); + } +#endif + + // Prevent writes beyond our allocation. + if (copy_cnt > dest_poly->num_alloc - dest_poly->numpts) + { +#ifdef DEBUG_HWBSP + CONS_Debug(DBG_RENDER, "ERROR wpoly_append, exceeds dest allocation, copy_cnt= %i, copy_to= %i, dest numpts= %i\n", + copy_cnt, dest_poly->numpts+1, dest_poly->numpts); +#endif + copy_cnt = dest_poly->num_alloc - dest_poly->numpts; // limit the append + } + + if (copy_cnt <= 0) + { +#ifdef DEBUG_HWBSP + CONS_Debug(DBG_RENDER, "ERROR wpoly_append, zero copy cnt, copy_cnt= %i\n", + copy_cnt); +#endif + return; + } + + pvp = & dest_poly->ppts[dest_poly->numpts]; // append + dest_poly->numpts += copy_cnt; // before copy_cnt gets decremented + + n = src_poly->numpts - copy_from; // vertexes to end of poly + if (copy_cnt > n) // copy to end of poly, then rollover + { + // Copy first portion, up to end of poly. + memcpy( pvp, &(src_poly->ppts[copy_from]), n*sizeof(void*)); + pvp += n; + // Rollover to start of src_poly + copy_cnt -= n; + copy_from = 0; + } + +#ifdef DEBUG_HWBSP + if (copy_from + copy_cnt > src_poly->numpts ) // after wrap + { + CONS_Debug(DBG_RENDER, "ERROR wpoly_append, exceeds src bounds, copy_from= %i, copy_cnt= %i, src numpts= %i\n", + copy_from, copy_cnt, src_poly->numpts); + } +#endif +#ifdef DEBUG_HWBSP + if ((pvp + copy_cnt) > (dest_poly->ppts + dest_poly->numpts)) // after wrap + { + CONS_Debug(DBG_RENDER, "ERROR wpoly_append, exceeds dest bounds, copy_to= %i, copy_cnt= %i, numpts= %i\n", + (pvp - dest_poly->ppts), copy_cnt, dest_poly->numpts); + } +#endif + + if (copy_cnt > 0) + { + memcpy(pvp, &(src_poly->ppts[copy_from]), copy_cnt*sizeof(void*)); + } +} + + +// Insert some new vertex, and then, +// copy some of another poly to the destination poly. +// v1, v2 : polyvertex to be inserted as first vertex of poly, in this order +// src_poly : copy from src_poly +// copy_from, copy_cnt : the indexes of the vertexes to copy +// dest_poly : the destination poly +static void wpoly_split_copy(polyvertex_t * v1, polyvertex_t * v2, + wpoly_t * src_poly, INT32 copy_from, INT32 copy_cnt, + /*OUT*/ wpoly_t * dest_poly) +{ + polyvertex_t ** pvp; + INT32 n = 0; + + // Count the dest vertexes. + if (v1) + n++; + + if (v2) + n++; + + // Free old content, new allocation. + wpoly_free(dest_poly); +#ifdef DEBUG_TRACE + dest_poly->id3 = dest_poly->id2; + dest_poly->id2 = dest_poly->id1; + dest_poly->id1 = poly_id++; +#endif + wpoly_init_alloc(n + copy_cnt, dest_poly); + + // First two points of the dest_poly are the dividing seg. + dest_poly->numpts = n; // v1 and v2 + pvp = dest_poly->ppts; + + if (v1) + *pvp++ = v1; + + if (v2) + *pvp++ = v2; + + wpoly_append(src_poly, copy_from, copy_cnt, /*OUT*/ dest_poly); + } + +// Insert vertexes into the destination poly, cutout some vertexes, save some. +// v1, v2 : polyvertex to be inserted as first vertex of poly, in this order +// v_from, v_cnt : the indexes of the vertexes to save +// xpoly : the source and destination poly +static void wpoly_insert_cut(polyvertex_t *v1, polyvertex_t *v2, INT32 v_from, INT32 v_cnt, /*INOUT*/ wpoly_t *xpoly) +{ + wpoly_t tmp_poly; + + tmp_poly = *xpoly; // save ptrs and sizes + xpoly->ppts = NULL; // so does not get freed + wpoly_split_copy(v1, v2, &tmp_poly, v_from, v_cnt, /*OUT*/ xpoly); + wpoly_free(&tmp_poly); // release saved poly content +} + + +// Insert a vertex into the destination poly, at a position. +// v1 : polyvertex to be inserted +// v_at : the index where v1 is inserted +// xpoly : the source and destination poly +static void wpoly_insert_vert(polyvertex_t *v1, INT32 v_at, /*INOUT*/ wpoly_t *xpoly) +{ + wpoly_t tmp_poly; + INT32 numpts = xpoly->numpts; + + if (v_at > numpts) + return; + + tmp_poly = *xpoly; // save ptrs and sizes + // Copy back from tmp_poly to xpoly +#ifdef DEBUG_TRACE + xpoly->id3 = xpoly->id2; + xpoly->id2 = xpoly->id1; + xpoly->id1 = poly_id++; + + if (trigger_trace) + { + CONS_Debug(DBG_RENDER, " Insert creates poly id=%i,%i,%i\n", xpoly->id1, xpoly->id2, xpoly->id3 ); + } +#endif + wpoly_init_alloc(numpts + 1, xpoly); + xpoly->numpts = numpts + 1; + + if (v_at > 0) + { + memcpy(&(xpoly->ppts[0]), &(tmp_poly.ppts[0]), sizeof(void*) * v_at); + } + + xpoly->ppts[v_at] = v1; // insert + + if (v_at < numpts) + { + memcpy(&(xpoly->ppts[v_at + 1]), &(tmp_poly.ppts[v_at]), sizeof(void*) * (numpts - v_at)); + } + + wpoly_free(&tmp_poly); // release saved poly content +} + +// ---- Subsectors + +// Array of poly_subsector_t, +// Index by bsp subsector num, 0.. num_poly_subsector-1 +poly_subsector_t *poly_subsectors = NULL; +wpoly_t *wpoly_subsectors = NULL; // working subsectors + + +// extra subsectors are subsectors without segs, added for the plane polygons +#define NUM_EXTRA_SUBSECTORS 50 +size_t num_poly_subsector = 0; +size_t num_alloc_poly_subsector = 0; + +// ========================================================================== +// FLOOR & CEILING CONVEX POLYS GENERATION +// ========================================================================== + +#ifdef DEBUG_HWBSP +//debug counters +static int nobackpoly_cnt = 0; +static int skipcut_cnt = 0; +static int total_subsecpoly_cnt = 0; +#endif + +// -------------------------------------------------------------------------- +// Polygon fast alloc / free +// -------------------------------------------------------------------------- + +#define ZPLANALLOC + +#ifndef ZPLANALLOC +#define POLY_ALLOCINC 4096 +#define POLY_VERTINC 256 +static byte* gr_polypool = NULL; +static unsigned int gr_polypool_free = 0; +#endif + +static void HWR_Freepolysubsectors(void); + +// only between levels, clear poly pool +static void HWR_ClearPolys(void) +{ + Z_FreeTags(PU_HWRPLANE, PU_HWRPLANE); +#ifndef ZPLANALLOC + gr_polypool = NULL; + gr_polypool_free = 0; +#endif + poly_vert = NULL; + polyvert_store = NULL; +} + +// allocate pool for fast alloc of polys +void HWR_InitPolyPool(void) +{ + HWR_ClearPolys(); +} + +void HWR_FreePolyPool(void) +{ + HWR_Freepolysubsectors(); + HWR_ClearPolys(); +} + +static poly_t* HWR_AllocPoly(INT32 numpts) +{ + poly_t* p; + size_t size; + + size = sizeof(poly_t) + sizeof(polyvertex_t) * numpts; +#ifdef ZPLANALLOC + p = Z_Malloc(size, PU_HWRPLANE, NULL); +#else + if (gr_polypool_free < size) + { + // Allocate another pool. + // Z_FreeTags reclaims the leftover memory of previous pool. + gr_polypool_free = POLY_ALLOCINC; + gr_polypool = Z_Malloc(gr_polypool_free, PU_HWRPLANE, NULL); + } + + p = (poly_t*) gr_polypool; + gr_polypool += size; + gr_polypool_free -= size; +#endif + p->numpts = numpts; + + return p; +} + +#ifdef DEBUG_HWBSP +// print poly for debugging +void pwpoly(wpoly_t *poly) +{ + int i; + for (i = 0; inumpts; i++) + { + if (poly->ppts[i]) + printf("(%6.2f,%6.2f)", poly->ppts[i]->x, poly->ppts[i]->y); + } + + printf("\n"); +} + +// print poly for debugging +void ppoly( poly_t * poly ) +{ + int i; + for ( i = 0; inumpts; i++ ) + printf( "(%6.2f,%6.2f)", poly->pts[i].x, poly->pts[i].y); + printf("\n"); +} +#endif + +// The BSP has the partition lines that define the subsectors. They do not +// exist anywhere else. + +// The subsectors of the BSP only have segs that are parts of linedefs. +// The subsector segs are not in any special order. +// Subsectors with 0 segs are skipped in building the BSP, so those are missing. +// Such subsectors are defined only by the dividing lines. +// The subsector of the BSP often encloses some adjoining void space. + +// Deep water in BSP: The deep water sector uses a linedef referencing a +// remote sector. The BSP will have extra dividing line polygon splits that +// are useless. The BSP builder got confused by the linedefs with remote +// sector references. +// This will result in an attempted polygon split that misses entirely. + + +// --- Divide line + +typedef enum +{ + DVL_none, // no divide + DVL_v1, // divide at v1 end of segment + DVL_mid, // divide between v1 and v2 + DVL_v2, // divide at v2 end of segment +} divline_e; typedef struct { @@ -41,138 +629,70 @@ typedef struct float dx, dy; } fdivline_t; -// ========================================================================== -// FLOOR & CEILING CONVEX POLYS GENERATION -// ========================================================================== - -//debug counters -static INT32 nobackpoly = 0; -static INT32 skipcut = 0; -static INT32 totalsubsecpolys = 0; - -// -------------------------------------------------------------------------- -// Polygon fast alloc / free -// -------------------------------------------------------------------------- -//hurdler: quick fix for those who wants to play with larger wad - -#define ZPLANALLOC -#ifndef ZPLANALLOC -//#define POLYPOOLSIZE 1024000 // may be much over what is needed -/// \todo check out how much is used -static size_t POLYPOOLSIZE = 1024000; - -static UINT8 *gl_polypool = NULL; -static UINT8 *gl_ppcurrent; -static size_t gl_ppfree; -#endif - -// only between levels, clear poly pool -static void HWR_ClearPolys(void) +#ifdef DEBUG_HWBSP +static void fdivline_dump(const char * str, fdivline_t * dl) { -#ifndef ZPLANALLOC - gl_ppcurrent = gl_polypool; - gl_ppfree = POLYPOOLSIZE; -#endif + polyvertex_t v1; + v1.x = dl->x; + v1.y = dl->y; + CONS_Debug(DBG_RENDER, "%s", str); + polyvertex_dump( & v1); + CONS_Debug(DBG_RENDER, " to ", str); + v1.x += dl->dx; + v1.y += dl->dy; + polyvertex_dump( & v1); + CONS_Debug(DBG_RENDER, " slope (%f, %f)\n", dl->dx, dl->dy); } +#endif -// allocate pool for fast alloc of polys -void HWR_InitPolyPool(void) +typedef struct { -#ifndef ZPLANALLOC - INT32 pnum; + polyvertex_t divpt; + polyvertex_t * vertex; // when same as segment endpoint + float divfrac; // how far along the partline vector is the crossing point + int before, after; // index modifiers for hitting a vertex + boolean at_vert; // crossing point is at a vertex +} div_result_t; - //hurdler: quick fix for those who wants to play with larger wad - if ((pnum = M_CheckParm("-polypoolsize"))) - POLYPOOLSIZE = atoi(myargv[pnum+1])*1024; // (in kb) - - CONS_Debug(DBG_RENDER, "HWR_InitPolyPool(): allocating %d bytes\n", POLYPOOLSIZE); - gl_polypool = malloc(POLYPOOLSIZE); - if (!gl_polypool) - I_Error("HWR_InitPolyPool(): couldn't malloc polypool\n"); - HWR_ClearPolys(); -#endif -} - -void HWR_FreePolyPool(void) +#ifdef DEBUG_HWBSP +static void divresult_dump( onst char * str, div_result_t * dr) { -#ifndef ZPLANALLOC - if (gl_polypool) - free(gl_polypool); - gl_polypool = NULL; -#endif + CONS_Debug(DBG_RENDER, "%s", str); + CONS_Debug(DBG_RENDER, " CROSS %6.4f BEFORE v1+%i AFTER v1+%i ", dr->divfrac, dr->before, dr->after); + if (dr->at_vert ) + { + CONS_Debug(DBG_RENDER, " AT"); + } + if (dr->vertex ) + { + CONS_Debug(DBG_RENDER, " SEGPT"); + polyvertex_dump( dr->vertex); + } + else + { + CONS_Debug(DBG_RENDER, " PT"); + polyvertex_dump( &dr->divpt); + } + CONS_Debug(DBG_RENDER, "\n"); } - -static poly_t *HWR_AllocPoly(INT32 numpts) -{ - poly_t *p; - size_t size = sizeof (poly_t) + sizeof (polyvertex_t) * numpts; -#ifdef ZPLANALLOC - p = Z_Malloc(size, PU_HWRPLANE, NULL); -#else -#ifdef PARANOIA - if (!gl_polypool) - I_Error("Used gl_polypool without init!\n"); - if (!gl_ppcurrent) - I_Error("gl_ppcurrent == NULL!\n"); #endif - if (gl_ppfree < size) - I_Error("HWR_AllocPoly(): no more memory %u bytes left, %u bytes needed\n\n%s\n", - gl_ppfree, size, "You can try the param -polypoolsize 2048 (or higher if needed)"); - - p = (poly_t *)gl_ppcurrent; - gl_ppcurrent += size; - gl_ppfree -= size; -#endif - p->numpts = numpts; - return p; -} - -static polyvertex_t *HWR_AllocVertex(void) -{ - polyvertex_t *p; - size_t size = sizeof (polyvertex_t); -#ifdef ZPLANALLOC - p = Z_Malloc(size, PU_HWRPLANE, NULL); -#else - if (gl_ppfree < size) - I_Error("HWR_AllocVertex(): no more memory %u bytes left, %u bytes needed\n\n%s\n", - gl_ppfree, size, "You can try the param -polypoolsize 2048 (or higher if needed)"); - - p = (polyvertex_t *)gl_ppcurrent; - gl_ppcurrent += size; - gl_ppfree -= size; -#endif - return p; -} - -/// \todo polygons should be freed in reverse order for efficiency, -/// for now don't free because it doesn't free in reverse order -static void HWR_FreePoly(poly_t *poly) -{ -#ifdef ZPLANALLOC - Z_Free(poly); -#else - const size_t size = sizeof (poly_t) + sizeof (polyvertex_t) * poly->numpts; - memset(poly, 0x00, size); - //mempoly -= polysize; -#endif -} - - -// Return interception along bsp line, +// Return interception along bsp line (partline), // with the polygon segment -// -static float bspfrac; -static polyvertex_t *fracdivline(fdivline_t *bsp, polyvertex_t *v1, - polyvertex_t *v2) + +// BOOMEDIT.WAD has a vertex error of .21 +#define DIVLINE_VERTEX_DIFF 0.45f + +// partline : the dividing line +// p1, p2 : the polygon segment +// result : the result of the division +static divline_e fracdivline(fdivline_t* partline, polyvertex_t* v1, polyvertex_t* v2, + /*OUT*/ div_result_t * result) { - static polyvertex_t pt; double frac; - double num; - double den; - double v1x,v1y,v1dx,v1dy; - double v2x,v2y,v2dx,v2dy; + double den; // numerator, denominator + double v1x, v1y, v1dx, v1dy; // polygon side vector, v1->v2 + double v3x, v3y, v3dx, v3dy; // partline vector // a segment of a polygon v1x = v1->x; @@ -181,395 +701,1314 @@ static polyvertex_t *fracdivline(fdivline_t *bsp, polyvertex_t *v1, v1dy = v2->y - v1->y; // the bsp partition line - v2x = bsp->x; - v2y = bsp->y; - v2dx = bsp->dx; - v2dy = bsp->dy; + v3x = partline->x; + v3y = partline->y; + v3dx = partline->dx; + v3dy = partline->dy; - den = v2dy*v1dx - v2dx*v1dy; - if (fabsf((float)den) < 1.0E-36f) // avoid checking exactly for 0.0 - return NULL; // parallel + den = v3dy*v1dx - v3dx*v1dy; + if (fabs(den) < 1.0E-36f) // avoid check of float for exact 0 + return DVL_none; // partline and polygon side are effectively parallel // first check the frac along the polygon segment, // (do not accept hit with the extensions) - num = (v2x - v1x)*v2dy + (v1y - v2y)*v2dx; - frac = num / den; - if (frac < 0.0l || frac > 1.0l) - return NULL; + const double num1 = (v3x - v1x)*v3dy + (v1y - v3y)*v3dx; + frac = num1 / den; + + // 0= cross at v1, 1.0= cross at v2 + if (frac < 0.0 || frac > 1.0) // double + return DVL_none; // not within the polygon side // now get the frac along the BSP line // which is useful to determine what is left, what is right - num = (v2x - v1x)*v1dy + (v1y - v2y)*v1dx; - frac = num / den; - bspfrac = (float)frac; + double num2 = (v3x - v1x)*v1dy + (v1y - v3y)*v1dx; + result->divfrac = num2 / den; // how far along partline vector - // find the interception point along the partition line - pt.x = (float)(v2x + v2dx*frac); - pt.y = (float)(v2y + v2dy*frac); + // [WDJ] find the interception point along the segment. + // It should be slightly more accurate because it is always closer to the + // crossing point than arbitrary positions on the partition line. + result->divpt.x = v1x + v1dx*frac; + result->divpt.y = v1y + v1dy*frac; - return &pt; + // Determine if dividing point is one of the end vertex. + // Set before and after indexes, relative to v1 index. + if (frac < 0.05 // double + && SameVertex(&result->divpt, v1, DIVLINE_VERTEX_DIFF)) + { + result->vertex = v1; + result->before = -1; // before v1 + result->after = 1; // at v2 + result->at_vert = true; + return DVL_v1; + } + + if (frac > 0.95 // double + && SameVertex(&result->divpt, v2, DIVLINE_VERTEX_DIFF)) + { + result->vertex = v2; + result->before = 0; // at v1 + result->after = 2; // after v2 + result->at_vert = true; + return DVL_v2; + } + + // Middle split + result->vertex = NULL; + result->before = 0; // at v1 + result->after = 1; // at v2 + result->at_vert = false; + return DVL_mid; } -// if two vertice coords have a x and/or y difference -// of less or equal than 1 FRACUNIT, they are considered the same -// point. Note: hardcoded value, 1.0f could be anything else. -static boolean SameVertice (polyvertex_t *p1, polyvertex_t *p2) +// Adapted from function in prboom. +// Point is to rightside of divline when result > 0, +// but result is multiplied by length of divline. +// Returns near 0, when point is on, or nearly on, the divline. +static inline float point_rightside(fdivline_t * dl, polyvertex_t * v4) { -#if 0 - float diff; - diff = p2->x - p1->x; - if (diff < -1.5f || diff > 1.5f) - return false; - diff = p2->y - p1->y; - if (diff < -1.5f || diff > 1.5f) - return false; -#elif 0 - if (p1->x != p2->x) - return false; - if (p1->y != p2->y) - return false; -#elif 0 - if (fabsf( p2->x - p1->x ) > 1.0E-36f ) - return false; - if (fabsf( p2->y - p1->y ) > 1.0E-36f ) - return false; -#else -#define DIVLINE_VERTEX_DIFF 0.45f - float ep = DIVLINE_VERTEX_DIFF; - if (fabsf( p2->x - p1->x ) > ep ) - return false; - if (fabsf( p2->y - p1->y ) > ep ) - return false; -#endif - // p1 and p2 are considered the same vertex - return true; + // Cross product of dl and vector dl->(x,y) to v4, + // is > 0 when v4 is to right side of divline. + // Viewed along divline from vertex, looking towards positive dx,dy. + // If divline is rotated until dy>0 and dx = 0, then true when rotated + // vertex position is to the right of the divline (v4->x > dl->x). + return (float) + ( (((double)(v4->x) - (double)(dl->x)) * (double)(dl->dy)) + - (((double)(v4->y) - (double)(dl->y)) * (double)(dl->dx))); } +// Return the cross product of the vector p1->p2, and p1->v4. +// The cross product is > 0 when v4 is to the right side of the vector. +// If the coordinates are rotated until the vector dy>0 and dx = 0, then the +// cross product is > 0 when v4 is to the right of the vector. +static inline double cross_product(polyvertex_t * p1, polyvertex_t * p2, polyvertex_t * v4) +{ + return + ( ((double)(v4->x) - (double)(p1->x)) * ((double)(p2->y) - (double)(p1->y)) + - ((double)(v4->y) - (double)(p1->y)) * ((double)(p2->x) - (double)(p1->x)) + ); +} -// split a _CONVEX_ polygon in two convex polygons +#ifdef POLYTILE +// Polytile list +// SplitPoly searches for the other poly with the same vertexes when it +// splits a poly segment, and adds the same vertex there too. +// This prevents any cracks from forming. + +// Hold all polygons that tile the level map. +#define POLYTILE_NUM_POLY 256 +typedef struct polytile_store_s +{ + struct polytile_store_s *next; // link for search + INT32 num_tile_used; + wpoly_t *tile[POLYTILE_NUM_POLY]; +} polytile_store_t; + +// These are freed by Z_Free( PU_HWRPLANE ). +polytile_store_t *polytile_store = NULL; +polytile_store_t *polytile_free = NULL; + +// Cleanup after usage. +static void polytile_clean(void) +{ + while (polytile_store) + { + polytile_store_t *ptp = polytile_store; + polytile_store = ptp->next; + Z_Free(ptp); + } +} + +// Save the poly ptr within the polytile lists. +static void polytile_enter(wpoly_t * poly) +{ + polytile_store_t *ptp = polytile_store; + + if (!cv_glpolytile.value) + return; + + if (poly->numpts == 0) + return; // do not enter NULL poly + + if (!polytile_store + || (polytile_store->num_tile_used >= POLYTILE_NUM_POLY)) + { + // Need another storage unit. + if (polytile_free) + { + polytile_store = polytile_free; + polytile_free = polytile_free->next; + } + else + { + polytile_store = Z_Malloc(sizeof(polytile_store_t), PU_HWRPLANE, NULL); + } + + polytile_store->next = ptp; // link for search + polytile_store->num_tile_used = 0; + } + + polytile_store->tile[ polytile_store->num_tile_used++ ] = poly; +} + +static void polytile_remove(wpoly_t * poly) +{ + polytile_store_t *ptp; + wpoly_t **wpp; + wpoly_t *lp; + INT32 i; + + // Search poly tiling. + ptp = polytile_store; + while (ptp) + { + // Search for poly in all polytile_store_t + wpp = & ptp->tile[0]; + for (i = ptp->num_tile_used-1; i >= 0; i--) + { + if (*wpp == poly) + goto found; + + wpp++; + } + ptp = ptp->next; + } + return; // not found + +found: + // Removing it gets complicated due to need to condense the list for searching. + // Move the last poly to the empty spot. There must be a last poly entered. + lp = polytile_store->tile[polytile_store->num_tile_used-1]; + *wpp = lp; // keep store compacted (ok if *wpp == lp already) + + // Remove last poly spot. + polytile_store->num_tile_used --; + if (polytile_store->num_tile_used == 0) + { + // Went empty, put on free list. + ptp = polytile_store; + polytile_store = ptp->next; + ptp->next = polytile_free; + polytile_free = ptp; + } +} + +// Seach polytile and add the new vertex between the vertex of the poly side. +// newvert : add this vertex +// poly : the poly being split +// i1, i2 : indexes of the split side +static void add_vertex_between(polyvertex_t * newvert, wpoly_t * poly, INT32 i1, INT32 i2) +{ + polytile_store_t *ptp; + wpoly_t *wp; + INT32 t, j2; + polyvertex_t *s1, *s2; + polyvertex_t *v1 = poly->ppts[i1]; + polyvertex_t *v2 = poly->ppts[i2]; + + if (!cv_glpolytile.value) + return; + +// shut up compiler +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wfloat-equal" + // Do not need to enter a vertex in vert or horz segments. + // Those do not cause problems with cracks. + if ((newvert->x == v1->x) && (newvert->x == v2->x)) + return; + + if ((newvert->y == v1->y) && (newvert->y == v2->y)) + return; +#pragma GCC diagnostic pop + + // Search poly tiling. + ptp = polytile_store; + while (ptp) + { + // Search for poly in all polytile_store_t + for (t = ptp->num_tile_used-1; t >= 0; t--) + { + wp = ptp->tile[t]; + if (wp == poly) + continue; // we already know this poly has v1,v2. + + // Search this poly for v1,v2 vertex in opposite order. + s1 = wp->ppts[wp->numpts-1]; // last vertex + for (j2 = 0; j2 < wp->numpts; j2++) + { + s2 = wp->ppts[j2]; + + if (s2 == v1) + { + if (s1 == v2) + goto found; + + break; // cannot find v1 a second time in same poly + } + + s1 = s2; + } + + } + ptp = ptp->next; + } + return; // not found + +found: +#ifdef DEBUG_TRACE + if (trigger_trace) + { + CONS_Debug(DBG_RENDER, " Insert vertex:" ); + polyvertex_dump( newvert); + CONS_Debug(DBG_RENDER, " found in %i:", wp->id1); + polyvertex_dump( v1); + polyvertex_dump( v2); + CONS_Debug(DBG_RENDER, "\n"); + } +#endif + // Insert newvert between s1 and s2 (opposite order or v1 v2). + // (j2-1) is vertex index of s1, j2 is vertex index of s2. + wpoly_insert_vert(newvert, j2, /*INOUT*/ wp); + // Result is in the same wp. +} +#endif + +// Split a _CONVEX_ polygon in two convex polygons. +// poly : polygon to be split by divline // outputs: // frontpoly : polygon on right side of bsp line // backpoly : polygon on left side // -static void SplitPoly (fdivline_t *bsp, //splitting parametric line - poly_t *poly, //the convex poly we split - poly_t **frontpoly, //return one poly here - poly_t **backpoly) //return the other here +// Called from: HWR_WalkBSPNode +static void SplitPoly(fdivline_t *dlnp, wpoly_t *poly, /*OUT*/ wpoly_t *frontpoly, wpoly_t *backpoly) { - INT32 i,j; - polyvertex_t *pv; + // Split poly at A and B. + wpoly_t *polyA; // the poly from A to B, clockwise + wpoly_t *polyB; // the poly from B to A, clockwise + INT32 n, i, j; +#ifdef POLYTILE + INT32 A_before_wrap, B_after_wrap; +#endif + divline_e dle; + div_result_t A, B; // dividing points + div_result_t *result; - INT32 ps = -1,pe = -1; - INT32 nptfront,nptback; - polyvertex_t vs = {0,0,0}; - polyvertex_t ve = {0,0,0}; - polyvertex_t lastpv = {0,0,0}; - float fracs = 0.0f,frace = 0.0f; //used to tell which poly is on - // the front side of the bsp partition line - INT32 psonline = 0, peonline = 0; +#ifdef DEBUG_TRACE + if (trigger_trace) + { + fdivline_dump("SplitPoly: divline", dlnp); + wpoly_dump("Poly", poly); + } +#endif + if (poly->numpts < 3) + goto poly_degenerate; // not a poly, cannot split it + + result = &A; // Setup to get crossing point A for (i = 0; i < poly->numpts; i++) { - j = i + 1; - if (j == poly->numpts) j = 0; + // i, j are one side of the poly + j = i+1; + if (j == poly->numpts) + j = 0; // wrap poly - // start & end points - pv = fracdivline(bsp, &poly->pts[i], &poly->pts[j]); - - if (pv == NULL) + // Find A and B points + dle = fracdivline(dlnp, poly->ppts[i], poly->ppts[j], /*OUT*/ result); + if (dle == DVL_none) continue; - if (ps < 0) + // have dividing pt + if (result == &A) { - // first point - ps = i; - vs = *pv; - fracs = bspfrac; - } - else - { - //the partition line traverse a junction between two segments - // or the two points are so close, they can be considered as one - // thus, don't accept, since split 2 must be another vertex - if (SameVertice(pv, &lastpv)) - { - if (pe < 0) - { - ps = i; - psonline = 1; - } - else - { - pe = i; - peonline = 1; - } - } - else - { - if (pe < 0) - { - pe = i; - ve = *pv; - frace = bspfrac; - } - else - { - // a frac, not same vertice as last one - // we already got pt2 so pt 2 is not on the line, - // so we probably got back to the start point - // which is on the line - if (SameVertice(pv, &vs)) - psonline = 1; - break; - } - } + // Split at A + // Dependent upon dle, setup in fracdivline. + A.before += i; + A.after += i; + result = &B; // Setup to get crossing point B + continue; } - // remember last point intercept to detect identical points - lastpv = *pv; + // The partition line can cross at a vertex, between two segments, + // or the two points are so close, they can be considered as one. + // Crossing point B must be another vertex. + + // When ( dle == DVL_v1 || dle == DVL_v2 ) then test for same vertex. + // It is NULL for DVL_mid. + // When dividing point is at a vertex, it is found at next segment too. + if (B.vertex // ( dle == DVL_v1 || dle == DVL_v2 ) + && B.vertex == A.vertex) + continue; + + // Split at B + // Dependent upon dle, setup in fracdivline. + B.before += i; + B.after += i; // linear, no rollover + goto split_poly; // got 2 points } - // no split: the partition line is either parallel and + goto no_split; + +split_poly: +#ifdef POLYTILE + A_before_wrap = (A.before < 0)? (A.before + poly->numpts) : A.before; + B_after_wrap = (B.after >= poly->numpts)? (B.after - poly->numpts) : B.after; +#endif + + // Less aggressive same vertex, to avoid kinking line. +//#define SAME_VERTEX_DIST 0.01f +#define SAME_VERTEX_DIST 0.08f + if (A.vertex == NULL) + { + A.vertex = store_polyvertex(&A.divpt, SAME_VERTEX_DIST); +#ifdef POLYTILE + add_vertex_between(A.vertex, poly, A_before_wrap, A.after); +#endif + } + + if (B.vertex == NULL) + { + B.vertex = store_polyvertex(&B.divpt, SAME_VERTEX_DIST); +#ifdef POLYTILE + add_vertex_between(B.vertex, poly, B.before, B_after_wrap); +#endif + } + + // The frontpoly is the one on the 'right' side + // of the partition line. + if (A.divfrac > B.divfrac) + { + polyA = frontpoly; + polyB = backpoly; + } + else + { + polyA = backpoly; + polyB = frontpoly; + } + + // Form PolyA + // Number of points from A to B clockwise. + n = B.before - A.after + 1; + if (n > 0) + { + // B, A, poly from A to B clockwise + wpoly_split_copy(B.vertex, A.vertex, poly, A.after, n, /*OUT*/ polyA); + } + else + polyA->numpts = 0; + + // Form PolyB + // Number of points from B to A clockwise. + n = A.before + poly->numpts - B.after + 1; + if (n > 0) + { + // A, B, poly from B to A clockwise + wpoly_split_copy(A.vertex, B.vertex, poly, + ((B.after < poly->numpts)? B.after : (B.after - poly->numpts)), + n, /*OUT*/ polyB); + } + else + polyB->numpts = 0; + +#ifdef DEBUG_HWBSP + // Test that frontpoly is to the right + if (frontpoly->numpts >= 2 + && (point_rightside( dlnp, frontpoly->ppts[2] ) < -0.5f )) + CONS_Printf( EMSG_warn, "SplitPoly: frontpoly on left side\n"); + + // Test that backpoly is to the left + if (backpoly->numpts >= 2 + && (point_rightside( dlnp, backpoly->ppts[2] ) > 0.5f )) + CONS_Printf( EMSG_warn, "SplitPoly: backpoly on right side\n"); +#endif +#ifdef DEBUG_TRACE + frontpoly->id3 = poly->id2; + frontpoly->id2 = poly->id1; + backpoly->id3 = poly->id2; + backpoly->id2 = poly->id1; + + if (trigger_trace) + { + wpoly_dump( " Front Poly", frontpoly); + wpoly_dump( " Back Poly", backpoly); + } +#endif + + return; + +no_split: + // no split : the partition line is either parallel and // aligned with one of the poly segments, or the line is totally // out of the polygon and doesn't traverse it (happens if the bsp // is fooled by some trick where the sidedefs don't point to // the right sectors) - if (ps < 0) + if (result == &A) { - //I_Error("SplitPoly: did not split polygon (%d %d)\n" - // "debugpos %d",ps,pe,debugpos); +#ifdef DEBUG_HWBSP + CONS_Debug(DBG_RENDER, "DEBUG: SplitPoly: divline missed entirely\n"); +#endif +#ifdef DEBUG_TRACE + // no results to dump +#endif // this eventually happens with 'broken' BSP's that accept // linedefs where each side point the same sector, that is: // the deep water effect with the original Doom - - /// \todo make sure front poly is to front of partition line? - - *frontpoly = poly; - *backpoly = NULL; - return; } - - if (pe < 0) + else if (A.vertex == NULL) { - //I_Error("SplitPoly: only one point for split line (%d %d)", ps, pe); - *frontpoly = poly; - *backpoly = NULL; - return; - } - if (pe <= ps) - I_Error("SplitPoly: invalid splitting line (%d %d)", ps, pe); - - // number of points on each side, _not_ counting those - // that may lie just one the line - nptback = pe - ps - peonline; - nptfront = poly->numpts - peonline - psonline - nptback; - - if (nptback > 0) - *backpoly = HWR_AllocPoly(2 + nptback); - else - *backpoly = NULL; - if (nptfront > 0) - *frontpoly = HWR_AllocPoly(2 + nptfront); - else - *frontpoly = NULL; - - // generate FRONT poly - if (*frontpoly) - { - pv = (*frontpoly)->pts; - *pv++ = vs; - *pv++ = ve; - i = pe; - do +#ifdef DEBUG_HWBSP + CONS_Debug(DBG_RENDER, + "DEBUG: SplitPoly: one new divide point %d (%6.2f,%6.2f) %d\n", + A.before, A.divpt.x, A.divpt.y, A.after); +#endif +#ifdef DEBUG_TRACE + if (trigger_trace) { - if (++i == poly->numpts) - i = 0; - *pv++ = poly->pts[i]; - } while (i != ps && --nptfront); + // Found a crossing A point, but did not find a B crossing. Should not happen. + divresult_dump( " CROSSING-A", &A); + } +#endif } - - // generate BACK poly - if (*backpoly) + else { - pv = (*backpoly)->pts; - *pv++ = ve; - *pv++ = vs; - i = ps; - do +#ifdef DEBUG_HWBSP + CONS_Debug(DBG_RENDER, + "DEBUG: SplitPoly: intersect at one vertex, %d\n", + A.before + 1); +#endif +#ifdef DEBUG_TRACE + if (trigger_trace) { - if (++i == poly->numpts) - i = 0; - *pv++ = poly->pts[i]; - } while (i != pe && --nptback); + // Found A point at vertex, but did not find a B crossing. Just touched a vertex. + divresult_dump( " CROSSING-A", &A); + } +#endif } - // make sure frontpoly is the one on the 'right' side - // of the partition line - if (fracs > frace) + // [WDJ] The front and back MUST match the BSP determination, else it will assign + // this poly to the wrong sector. + // May have 1 point on the line, and other points might be very close to the line. + // Can only trust a test of a point that is NOT on the line. + // Using a tstdist = 0.5f worked, but it might break on very small poly. + // Avactor.wad had polys with d = 9.09, 5552, 1.64, 1011, 14252, 0.0136, 0.00634, 14655, 3, + // -0.3044, 7, 1.53, etc.., + // and one poly with d = -0.023 with the rest of the points d > 0. + // One 14 point poly with divline len=8, had to test 9 points to find d = 36. + // Typical: divline len=70 d=6828, divline len=1.0 d=35, divline len=1.414 d=5. + // The result of point_rightside is proportional to the length of divline. + // The divline length can be from 1.414 to 14000. + const float tstd = (sqrtf((dlnp->dx * dlnp->dx) + (dlnp->dy * dlnp->dy))) / 2.0f; + float sumd = 0; // accumulated left or rightness. + + for (i = 0; i < poly->numpts; i++) { - poly_t *swappoly; - swappoly = *backpoly; - *backpoly = *frontpoly; - *frontpoly = swappoly; + // Look for a point that is obviously to the left or right. + float d = point_rightside(dlnp, poly->ppts[i]); // d > 0 is right + sumd += d; + + if (d > tstd) + goto poly_rightside; + + if (d < -tstd) + goto poly_leftside; } - HWR_FreePoly (poly); + // Did not find an obvious left or right point, then + if (sumd < 0) + goto poly_leftside; + +poly_rightside: + // poly is to rightside of divline. + wpoly_move(poly, frontpoly); + backpoly->numpts = 0; + return; + +poly_leftside: + // poly is to leftside of divline. + wpoly_move(poly, backpoly); + frontpoly->numpts = 0; + return; + +poly_degenerate: + // This cannot lead to any subsector polys. +#ifdef DEBUG_HWBSP + CONS_Debug(DBG_RENDER, "DEBUG: SplitPoly: degenerate poly has %d points\n", poly->numpts); +#endif + frontpoly->numpts = 0; + backpoly->numpts = 0; + return; +} + +// The BSP creates convex polygons, up to the point where it presents +// the subsector. The subsector segs may adjoin void space, and if that +// is cut out of the subsector polygon it might not be convex. +// Where the cutting seg does not entirely traverse the polygon, +// it should only cut over its length, not entirely to the other side +// of the polygon. Another seg, and maybe more, must be present to +// finish the cut to the other side of the polygon. There is no +// assurance that these segs will keep the polygon convex. +// Example: BOOMEDIT.WAD, subsector 206 is not convex after cutting segs. +// It is cut by seg linedef 104, and seg linedef 110. + +#define CUTOUT_NON_CONVEX 1 + +#ifdef CUTOUT_NON_CONVEX +// Force convex solution. +static void enforce_convex(wpoly_t * poly) +{ + polyvertex_t *rv1, *rv2, *rv3; + INT32 i1, i2, i3; + INT32 numpts = poly->numpts; + + for (i2 = 0; i2 < numpts;) + { + // Check that angle at i2 is less than 180. + i3 = i2 + 1; + if (i3 == numpts) + i3 = 0; + + rv3 = poly->ppts[i3]; + + i1 = i2 - 1; + if (i1 < 0) + i1 += numpts; + + rv1 = poly->ppts[i1]; + rv2 = poly->ppts[i2]; + + // cross product to check angle + if (cross_product( rv1, rv2, rv3 ) < 0) + { + // Remove the i2 vertex, to make the polygon more convex. + if ((i2+1) < numpts) + { // Shuffle down by 1 + memmove(&poly->ppts[i2], &poly->ppts[i2+1], sizeof(void*)*(numpts - (i2+1))); + } + numpts--; + + if (numpts <= 3) + break; + + // Have to repeat from i1 vertex, because that vertex angle + // changed too. + i2 = (i1 > 0) ? i1 : 0; + continue; + } + + i2++; + } + + poly->numpts = numpts; +} +#endif + +#ifdef CUTOUT_NON_CONVEX +// Some of the segs will only partially cross the polygon, but connect with +// another seg, and together will cross it, or form a corner. +// Trying to cut with these leads to an incomplete or non-convex polygon. +// Then latter seg cuts will have 3 or 4 crossings. +// Deal with linking the segs together separate from the seg cutting operation. + +typedef struct loose_seg_s +{ + struct loose_seg_s *next; + seg_t *seg; // the seg + polyvertex_t *p1, *p2; // clockwise order +} loose_seg_t; + +typedef struct seg_chain_s +{ + struct seg_chain_s *next; + loose_seg_t *first_seg; // head of seg-chain + loose_seg_t *last_seg; // tail of seg-chain + polyvertex_t *p1, *p2; // ends in clockwise order + boolean loose1, loose2; // loose ends of the seg-chain + INT32 num_seg; +} seg_chain_t; + +// Nothing to gain by making this a parameter, this saves param passing. +// Easier to deal with releasing memory. +static seg_chain_t *seg_chain = NULL; // Z_Malloc + +static void free_first_seg_chain(void) +{ + seg_chain_t *sctp; + loose_seg_t *lsp, *lsp2; + + sctp = seg_chain; + + if (sctp) + { + seg_chain = sctp->next; + // Free the list of loose segs + lsp = sctp->first_seg; + + while (lsp) + { + lsp2 = lsp->next; + Z_Free(lsp); + lsp = lsp2; + } + + Z_Free(sctp); + } +} + +// Clear out the seg_chain structure +static void clear_seg_chains(void) +{ + while (seg_chain) + free_first_seg_chain(); +} + +// The seg-chains may be in portions, due to entry order. +static void condense_seg_chains(void) +{ + seg_chain_t *sctp; + seg_chain_t *sctp2; + seg_chain_t *sctp2_prev; + polyvertex_t *pv2; + + // Search seg-chains for combinations. + sctp = seg_chain; + while (sctp) + { + if (sctp->loose2) // p2 is loose + { + // Search for pv2 as loose first in another seg-chain + pv2 = sctp->p2; + sctp2_prev = NULL; + sctp2 = seg_chain; + + while(sctp2) + { + if (sctp2 != sctp + && sctp2->loose1 + && sctp2->p1 == pv2) + goto combine; + + sctp2_prev = sctp2; + sctp2 = sctp2->next; + } + } + + sctp = sctp->next; + + continue; + +combine: + // Append sctp2 to last of sctp seg-chain. + sctp->last_seg->next = sctp2->first_seg; + sctp->last_seg = sctp2->last_seg; + sctp->p2 = sctp2->p2; + sctp->loose2 = sctp2->loose2; + sctp->num_seg += sctp2->num_seg; + + // Remove sctp2 + if (sctp2_prev) + sctp2_prev->next = sctp2->next; + else + seg_chain = sctp2->next; + + Z_Free( sctp2); + + continue; // with same sctp + } +} + +// Save loose segs in the seg_chain structure. +// B_A_order : the vertex order is B A for clockwise +static void save_loose_seg(seg_t *loose_seg, + polyvertex_t *vA, polyvertex_t *vB, + boolean looseA, boolean looseB, + boolean B_A_order) +{ + loose_seg_t *lsp; + seg_chain_t *sctp; + polyvertex_t *pv1, *pv2; + boolean loose1, loose2; + + // B_A_order is determined by the order of the polygon sides. + if (B_A_order) + { + pv1 = vB; + pv2 = vA; + loose1 = looseB; + loose2 = looseA; + } + else + { + pv1 = vA; + pv2 = vB; + loose1 = looseA; + loose2 = looseB; + } + + lsp = Z_Malloc(sizeof(loose_seg_t), PU_HWRPLANE, NULL); + lsp->seg = loose_seg; + lsp->p1 = pv1; + lsp->p2 = pv2; + lsp->next = NULL; + + // Search seg-chains + sctp = seg_chain; + while (sctp) + { + if (loose2 + && sctp->loose1 + && sctp->p1 == pv2) + { + // first of seg-chain + lsp->next = sctp->first_seg; + sctp->first_seg = lsp; + sctp->p1 = pv1; + sctp->loose1 = loose1; + sctp->num_seg++; + return; + } + + if (loose1 + && sctp->loose2 + && sctp->p2 == pv1 ) + { + // last of seg-chain + lsp->next = NULL; + sctp->last_seg->next = lsp; + sctp->last_seg = lsp; + sctp->p2 = pv2; + sctp->loose2 = loose2; + sctp->num_seg++; + return; + } + + sctp = sctp->next; + } + + // Need new seg-chain + sctp = Z_Malloc(sizeof(seg_chain_t), PU_HWRPLANE, NULL); + sctp->next = NULL; + sctp->num_seg = 1; + sctp->first_seg = lsp; + sctp->last_seg = lsp; + sctp->p1 = pv1; + sctp->p2 = pv2; + sctp->loose1 = loose1; + sctp->loose2 = loose2; + sctp->next = seg_chain; + seg_chain = sctp; } -// use each seg of the poly as a partition line, keep only the -// part of the convex poly to the front of the seg (that is, -// the part inside the sector), the part behind the seg, is -// the void space and is cut out -// -static poly_t *CutOutSubsecPoly(seg_t *lseg, INT32 count, poly_t *poly) +//#define SEG_CHAIN_2 + +// Apply the list of loose end seg chains. +// Return true if a possible non-convex cut is made. +static boolean apply_seg_chains(wpoly_t *poly) { - INT32 i, j; + boolean check_convex = false; + wpoly_t comb_poly; // combine seg-chain and poly + loose_seg_t *lsp; + seg_chain_t *sctp; + polyvertex_t *rv1, *rv2; + INT32 n, i1, i2; + INT32 numpts = poly->numpts; - polyvertex_t *pv; - - INT32 nump = 0, ps, pe; - polyvertex_t vs = {0, 0, 0}, ve = {0, 0, 0}, - p1 = {0, 0, 0}, p2 = {0, 0, 0}; - float fracs = 0.0f; - - fdivline_t cutseg; // x, y, dx, dy as start of node_t struct - - poly_t *temppoly; - - // for each seg of the subsector - for (; count--; lseg++) + // All the seg-chains are part of the polygon. If a seg-chain is + // outside the polygon, the polygon must be expanded to include it. + // Otherwise there will be cracks in the floor. + for (;;) { - line_t *line = lseg->linedef; + sctp = seg_chain; + if (!sctp) + break; - if (lseg->glseg) + rv1 = sctp->p1; + rv2 = sctp->p2; + i1 = i2 = numpts + 20; // invalid + +#ifndef SEG_CHAIN_2 + if (sctp->loose1 || sctp->loose2) + { + // FIXME: This needs to be handled. + goto reject; + } +#endif + +#ifdef SEG_CHAIN_2 + if (! sctp->loose1 ) +#endif + { + // Find original crossing point. + for ( i1 = 0; i1 < numpts; i1++) + { + if (poly->ppts[i1] == rv1) + break; + } + } + +#ifdef SEG_CHAIN_2 + if (! sctp->loose2 ) +#endif + { + // Find original crossing point. + for (i2 = 0; i2 < numpts; i2++) + { + if (poly->ppts[i2] == rv2) + break; + } + } + +#ifdef SEG_CHAIN_2 + if (sctp->loose1 && ! sctp->loose2 ) + { + } + + if (sctp->loose2 && ! sctp->loose1 ) + { + } + + if (sctp->loose1 && sctp->loose2 ) + { + } +#endif + + if ((i1 < numpts) && (i2 < numpts)) + { + // Insert points are still there. + // Alloc the right size comb_poly. + i2 ++; // copy after i2 + if (i2 >= numpts) + i2 -= numpts; + + n = i1 - i2; // not inclusive of i1 or i2 + if (n < 0) + n += numpts; + + wpoly_init_alloc(sctp->num_seg + 1 + n, & comb_poly); +#ifdef DEBUG_TRACE + comb_poly.id3 = poly->id2; + comb_poly.id2 = poly->id1; + comb_poly.id1 = poly_id++; +#endif + // Copy the seg-chain into the comb_poly. + polyvertex_t ** ppv = comb_poly.ppts; + + for (lsp = sctp->first_seg; lsp; lsp = lsp->next) + { + *ppv++ = lsp->p1; // rv1 to before rv2 + } + + *ppv = rv2; + comb_poly.numpts = sctp->num_seg + 1; + + // It is valid for n == 0, with num_seg > 1, + // and both end points on the same poly side. + if (n > 0) + { + // Save i2 to i1, which starts after rv2, to the comb_poly. + wpoly_append(poly, i2, n, /*OUT*/ & comb_poly); + } + + wpoly_move(&comb_poly, /*OUT*/ poly); // empty comb_poly + numpts = poly->numpts; + check_convex = true; + } +reject: + free_first_seg_chain(); + } + + return check_convex; +} + +#endif + +// Use each seg of the poly as a partition line, keep only the +// part of the convex poly to the front of the seg (that is, +// the part inside the sector). The part behind the seg, is +// the void space and is cut out. +// +// poly : surrounding convex polygon, non-destructive +// ssindex : subsec index, 0..(numsubsectors-1) +// Called from: HWR_SubsecPoly +static void CutOutSubsecPoly(INT32 ssindex, /*INOUT*/ wpoly_t* poly) +{ + subsector_t* sub; + seg_t *lseg; // array of seg + INT16 segcount; // number of seg in the array + + polyvertex_t *rv1, *rv2; // A,B or B,A + boolean cut_at_vert; + +#ifdef CUTOUT_NON_CONVEX + boolean check_convex = false; + boolean looseA, looseB; +#endif + + vertex_t *v1, *v2; + polyvertex_t p1, p2; + fdivline_t cutseg; // x,y,dx,dy as start of node_t struct + divline_e dle; + + div_result_t A, B; // dividing points + div_result_t *result; + INT32 poly_num_pts, ps, n; + INT32 i1, i2; + + poly_num_pts = poly->numpts; + sub = &subsectors[ssindex]; + segcount = sub->numlines; + lseg = &segs[sub->firstline]; + + // For each seg of the subsector + for (; segcount--; lseg++) + { + //x,y,dx,dy (like a divline) + const line_t *line = lseg->linedef; + + if (!line || lseg->glseg) continue; - //x,y,dx,dy (like a divline) - p1.x = FIXED_TO_FLOAT(lseg->side ? line->v2->x : line->v1->x); - p1.y = FIXED_TO_FLOAT(lseg->side ? line->v2->y : line->v1->y); - p2.x = FIXED_TO_FLOAT(lseg->side ? line->v1->x : line->v2->x); - p2.y = FIXED_TO_FLOAT(lseg->side ? line->v1->y : line->v2->y); + if (line->sidenum[1] != 0xffff) + { + if (sides[line->sidenum[0]].sector == sides[line->sidenum[1]].sector) + { + // Segs that are self-ref linedef do not cutout the subsector. +#ifdef DEBUG_HWBSP + CONS_Debug(DBG_RENDER, "CutOutSubsecPoly: self ref line %i\n", line - lines); +#endif + continue; + } + } + + if (lseg->side) + { // side 1 + v1 = line->v2; + v2 = line->v1; + } + else + { // side 0 + v1 = line->v1; + v2 = line->v2; + } + + p1.x = FIXED_TO_FLOAT(v1->x); + p1.y = FIXED_TO_FLOAT(v1->y); + p2.x = FIXED_TO_FLOAT(v2->x); + p2.y = FIXED_TO_FLOAT(v2->y); cutseg.x = p1.x; cutseg.y = p1.y; cutseg.dx = p2.x - p1.x; cutseg.dy = p2.y - p1.y; - // see if it cuts the convex poly - ps = -1; - pe = -1; - for (i = 0; i < poly->numpts; i++) + // See if it cuts the convex poly + result = &A; // Setup to get crossing point A + + for (i1 = 0; i1 < poly_num_pts; i1++) { - j = i + 1; - if (j == poly->numpts) - j = 0; + i2 = i1 + 1; + if (i2 >= poly_num_pts) + i2 = 0; - pv = fracdivline(&cutseg, &poly->pts[i], &poly->pts[j]); + // i1, i2 are one side of the poly + dle = fracdivline(&cutseg, poly->ppts[i1], poly->ppts[i2], /*OUT*/ result); - if (pv == NULL) + if (dle == DVL_none) continue; - if (ps < 0) + // have dividing pt + if (result == &A) { - ps = i; - vs = *pv; - fracs = bspfrac; + // Split at A + // Dependent upon dle, setup in fracdivline. + A.before += i1; + A.after += i1; + result = &B; // Setup to get crossing point B + continue; } - else - { - //frac 1 on previous segment, - // 0 on the next, - //the split line goes through one of the convex poly - // vertices, happens quite often since the convex - // poly is already adjacent to the subsector segs - // on most borders - if (SameVertice(pv, &vs)) - continue; - if (fracs <= bspfrac) - { - nump = 2 + poly->numpts - (i-ps); - pe = ps; - ps = i; - ve = *pv; - } - else - { - nump = 2 + (i-ps); - pe = i; - ve = vs; - vs = *pv; - } - //found 2nd point - break; - } + // The partition line can cross at a vertex, between two segments, + // or the two points are so close, they can be considered as one. + // Crossing point B must be another vertex. + + // When ( dle == DVL_v1 || dle == DVL_v2 ) then test for + // the same vertex. It is NULL for DVL_mid. + // When dividing point is at a vertex, is found at next segment too. + if (B.vertex // ( dle == DVL_v1 || dle == DVL_v2 ) + && B.vertex == A.vertex ) continue; + + // Split at B + // Dependent upon dle, setup in fracdivline. + B.before += i1; + B.after += i1; // linear, no rollover + goto cut_poly; // got 2 points } - // there was a split - if (ps >= 0) + // need 2 points + if (result == &B) { - //need 2 points - if (pe >= 0) - { - // generate FRONT poly - temppoly = HWR_AllocPoly(nump); - pv = temppoly->pts; - *pv++ = vs; - *pv++ = ve; - do - { - if (++ps == poly->numpts) - ps = 0; - *pv++ = poly->pts[ps]; - } while (ps != pe); - HWR_FreePoly(poly); - poly = temppoly; - } //hmmm... maybe we should NOT accept this, but this happens // only when the cut is not needed it seems (when the cut // line is aligned to one of the borders of the poly, and // only some times..) - else - skipcut++; - // I_Error("CutOutPoly: only one point for split line (%d %d) %d", ps, pe, debugpos); + // [WDJ] This happens often (27 times in Doom2 map01). + +#ifdef DEBUG_HWBSP + skipcut_cnt++; + //CONS_Debug(DBG_RENDER, + //"DEBUG: CutOutPoly: only one point for split line (%d %d)\n",ps,pe); +#endif + // It must have crossed at one polygon vertex. + // That is the only way to touch at only one point. + // The seg is usually slightly misaligned with the polygon side, + // leaving a crack in front of a wall. + // Let the seg-chains take care of it. It is important to + // extend the poly to all seg walls to eliminate cracks. + if (A.vertex == NULL) + { + A.vertex = store_polyvertex(&A.divpt, 0.25f); + } + + // Store the other vertex + B.vertex = store_polyvertex(((A.divfrac > 0.5) ? &p1 : &p2), 0.25f); + + // Get another point in the poly to rv1 + i2 = A.after; + + if (i2 >= poly_num_pts) + i2 = 0; + + rv1 = poly->ppts[i2]; + save_loose_seg(lseg, A.vertex, B.vertex, false, true, + cross_product(A.vertex, B.vertex, rv1) < 0); + + continue; } + continue; // no split by this segment + +cut_poly: + // Cuts that miss the polygon. + if ((A.divfrac < 0.0) && (B.divfrac < 0.0)) + continue; + + if ((A.divfrac > 1.0) && (B.divfrac > 1.0)) + continue; + + // Skip cuts that duplicate an existing side. + // Does not matter if goes across poly or not. + cut_at_vert = A.at_vert && B.at_vert; // cuts are at existing vertexes + + if (cut_at_vert) + { + // Skip if both crossings are on same polygon segment. + if (A.after + 1 == B.after) + continue; + + if (A.after + poly_num_pts == B.after + 1) + continue; + } + +#ifdef CUTOUT_NON_CONVEX +#define FRAC_EP 0.01f + // Cuts that may make the polygon non-convex. + looseA = (A.divfrac < (0.0 - FRAC_EP)) || (A.divfrac > (1.0f + FRAC_EP)); + + if (looseA) + { + // Cannot make normal cut, A end is loose. + if (cv_glpolyshape.value == 1) + continue; // fat polygons + + // Get vertex at A end of seg, v1 or v2 + A.vertex = store_polyvertex(((A.divfrac < 0.0) ? &p1 : &p2), 0.25f); + } + + looseB = (B.divfrac < (0.0 - FRAC_EP)) || (B.divfrac > (1.0f + FRAC_EP)); + + if (looseB) + { + // Cannot make normal cut, B end is loose. + if (cv_glpolyshape.value == 1) + continue; // fat polygons + + // Get vertex at B end of seg, v1 or v2 + B.vertex = store_polyvertex(((B.divfrac < 0.0 ) ? &p1 : &p2), 0.25f); + + if (looseA) // from A end + { + // Both ends are loose. + // All that can be done is to save the seg + save_loose_seg(lseg, A.vertex, B.vertex, true, true, + (B.divfrac < A.divfrac )); + continue; + } + } +#else + // Reject cuts that could make the polygon non-convex. + if ((A.divfrac < 0.0) || (A.divfrac > 1.0)) + continue; + + if ((B.divfrac < 0.0) || (B.divfrac > 1.0)) + continue; +#endif + + // Store new crossing vertex. + if (A.vertex == NULL) + { + A.vertex = store_polyvertex(&A.divpt, 0.25f); + } + + if (B.vertex == NULL) + { + B.vertex = store_polyvertex(&B.divpt, 0.25f); + } + +#ifdef CUTOUT_NON_CONVEX + if (looseA || looseB) + { + // Cutseg does not completely cross the poly. + // Save the seg + save_loose_seg(lseg, A.vertex, B.vertex, looseA, looseB, + (B.divfrac < A.divfrac)); + + // Insert the crossing vertex into the poly, as a marker. + // Must occur after the crossing vertex A, B are stored. + if (looseA) + { + // A is loose, B is crossing point. + if (B.at_vert) + continue; // Point is at existing vertex + + rv1 = B.vertex; + ps = B.before + 1; // insert point + } + else + { + // B is loose, A is crossing point. + if (A.at_vert) + continue; // Point is at existing vertex + + rv1 = A.vertex; + ps = A.before + 1; // insert point + } + rv2 = NULL; + n = poly_num_pts; + } + else +#endif + { + // Cutseg cuts across poly, at two points. + // Save poly on rightside of cutting seg. + if (B.divfrac < A.divfrac) + { + // B, A, poly from A to B clockwise + // Number of points from A to B clockwise. + n = B.before - A.after + 1; + ps = A.after; + rv1 = B.vertex; + rv2 = A.vertex; + } + else + { + // A, B, poly from B to A clockwise + // Number of points from B to A clockwise. + n = A.before + poly_num_pts - B.after + 1; + ps = B.after; + rv1 = A.vertex; + rv2 = B.vertex; + } + + // If cut at existing vertexes, and it has the same number of pts, + // then the cut is already an existing side, + // and the polygon is not going to change. + if (cut_at_vert && ((n+2) == poly_num_pts)) + continue; + } + + if (ps >= poly_num_pts) + ps -= poly_num_pts; + + wpoly_insert_cut( rv1, rv2, ps, n, poly); + poly_num_pts = poly->numpts; } - return poly; + +#ifdef CUTOUT_NON_CONVEX + // After all normal seg cuts, deal with the loose end segs. + if (seg_chain) + { + // Have some loose end segs. + condense_seg_chains(); + check_convex = apply_seg_chains( poly); + clear_seg_chains(); + } + + if (check_convex + && (cv_glpolyshape.value == 2) // Trim polygons + && (poly_num_pts > 3)) + { + // Need to check convex. + // Force convex solution, or split into convex. + enforce_convex(poly); + } +#endif } + // At this point, the poly should be convex and the exact -// layout of the subsector, it is not always the case, +// layout of the subsector. It is not always the case, // so continue to cut off the poly into smaller parts with // each seg of the subsector. // -static inline void HWR_SubsecPoly(INT32 num, poly_t *poly) +// ssindex : subsec index, 0..(numsubsectors-1) +// poly : surrounding convex polygon, non-destructive +// Called from HWR_WalkBSPNode +static void HWR_SubsecPoly(int ssindex, wpoly_t* poly) { - INT16 count; - subsector_t *sub; - seg_t *lseg; - - sub = &subsectors[num]; - count = sub->numlines; - lseg = &segs[sub->firstline]; - - if (poly) +#ifdef DEBUG_HWBSP +#ifdef DEBUG_TRACE { - poly = CutOutSubsecPoly (lseg,count,poly); - totalsubsecpolys++; - //extra data for this subsector - extrasubsectors[num].planepoly = poly; + subsector_t* sub = &subsectors[ssindex]; + + if (trigger_trace) + { + CONS_Debug(DBG_RENDER, "HWR_SubsecPoly: sector %i, subsector %i, poly->numpts= %i\n", sub->sector - sectors, ssindex, poly->numpts); + } } +#else + if (poly->numpts <= 0) + { + subsector_t* sub = &subsectors[ssindex]; + CONS_Debug(DBG_RENDER, "HWR_SubsecPoly: sector %i, subsector %i, poly->numpts= %i\n", sub->sector - sectors, ssindex, poly->numpts); + } +#endif +#endif + if (poly->numpts <= 0) + return; + + if (cv_glpolyshape.value > 0) + { + // Trim the subsector with the segs. + CutOutSubsecPoly(ssindex, /*INOUT*/ poly); + } + +#ifdef DEBUG_TRACE + if (trigger_trace) + { + wpoly_dump("Subsec poly", poly); + } +#endif +#ifdef DEBUG_HWBSP + total_subsecpoly_cnt++; +#endif } -// the bsp divline have not enouth presition -// search for the segs source of this divline -static inline void SearchDivline(node_t *bsp, fdivline_t *divline) +// The bsp divline does not have enough precision. +// Search for the segs source of this divline. +static inline void set_divline(node_t* bsp, fdivline_t *divline) { - divline->x = FIXED_TO_FLOAT(bsp->x); - divline->y = FIXED_TO_FLOAT(bsp->y); + divline->x = FIXED_TO_FLOAT(bsp->x); + divline->y = FIXED_TO_FLOAT(bsp->y); divline->dx = FIXED_TO_FLOAT(bsp->dx); divline->dy = FIXED_TO_FLOAT(bsp->dy); } @@ -605,422 +2044,930 @@ static void loading_status(void) #endif // poly : the convex polygon that encloses all child subsectors -static void WalkBSPNode(INT32 bspnum, poly_t *poly, UINT16 *leafnode, fixed_t *bbox) +// Recursive +// bspnum : children[] +// leafnode : & children [] +// Call HWR_WalkBSPNode_degen for degenerate case. +// Called from HWR_CreatePlanePolygons at load time. +static void HWR_WalkBSPNode(INT32 bspnum, wpoly_t* poly, UINT16 *leafnode, fixed_t *bbox) { - node_t *bsp; - poly_t *backpoly, *frontpoly; + node_t* bsp; + wpoly_t backpoly; + wpoly_t frontpoly; fdivline_t fdivline; polyvertex_t *pt; + INT32 subsecnum; // subsector index INT32 i; + (void)leafnode; // unused + +#ifdef DEBUG_TRACE + if (trigger_bsp_sector == 0xFFFFFFF2 ) + { + trigger_trace = 2; // trace all + } +#endif + // Found a subsector? if (bspnum & NF_SUBSECTOR) { - if (bspnum == -1) + // Subsector leaf: bspnum is a subsector number. + subsecnum = bspnum & ~NF_SUBSECTOR; + if ((size_t)subsecnum >= numsubsectors) + goto bad_subsector; + +#ifdef DEBUG_TRACE + if (trigger_bsp_sector < numsectors + && subsectors[subsecnum].sector == & sectors[trigger_bsp_sector] ) { - // BP: i think this code is useless and wrong because - // - bspnum==-1 happens only when numsubsectors == 0 - // - it can't happens in bsp recursive call since bspnum is a INT32 and children is UINT16 - // - the BSP is complet !! (there just can have subsector without segs) (i am not sure of this point) - - // do we have a valid polygon ? - if (poly && poly->numpts > 2) - { - CONS_Debug(DBG_RENDER, "Adding a new subsector\n"); - if (addsubsector == numsubsectors + NEWSUBSECTORS) - I_Error("WalkBSPNode: not enough addsubsectors\n"); - else if (addsubsector > 0x7fff) - I_Error("WalkBSPNode: addsubsector > 0x7fff\n"); - *leafnode = (UINT16)((UINT16)addsubsector | NF_SUBSECTOR); - extrasubsectors[addsubsector].planepoly = poly; - addsubsector++; - } - - //add subsectors without segs here? - //HWR_SubsecPoly(0, NULL); + CONS_Debug(DBG_RENDER, "BSP TRIGGER SECTOR %i: \n", trigger_bsp_sector); + trigger_trace = 1; } - else - { - HWR_SubsecPoly(bspnum & ~NF_SUBSECTOR, poly); - - //Hurdler: implement a loading status -#ifdef HWR_LOADING_SCREEN - if (ls_count-- <= 0) - { - ls_count = numsubsectors/50; - loading_status(); - } #endif - } + + HWR_SubsecPoly(subsecnum, poly); + + // Add the poly points into the bounding box. M_ClearBox(bbox); - poly = extrasubsectors[bspnum & ~NF_SUBSECTOR].planepoly; + for (i = 0; i < poly->numpts; i++) + { + pt = poly->ppts[i]; + M_AddToBox(bbox, (fixed_t)(pt->x * FRACUNIT), (fixed_t)(pt->y * FRACUNIT)); + } - for (i = 0, pt = poly->pts; i < poly->numpts; i++,pt++) - M_AddToBox(bbox, FLOAT_TO_FIXED(pt->x), FLOAT_TO_FIXED(pt->y)); +#ifdef POLYTILE + polytile_remove(poly); +#endif + wpoly_move(poly, /*OUT*/ & wpoly_subsectors[subsecnum]); + // poly is empty +#ifdef POLYTILE + polytile_enter(&wpoly_subsectors[subsecnum]); +#endif +#ifdef DEBUG_TRACE + if (trigger_trace == 1) // only turn off specifc subsector traces + trigger_trace = 0; +#endif + +#ifdef HWR_LOADING_SCREEN + //Hurdler: implement a loading status + if (ls_count-- <= 0) + { + ls_count = numsubsectors/50; + loading_status(); + } +#endif return; } - bsp = &nodes[bspnum]; - SearchDivline(bsp, &fdivline); - SplitPoly(&fdivline, poly, &frontpoly, &backpoly); - poly = NULL; + // Node reference: bspnum is another node of the tree. + if ((size_t)bspnum >= numnodes) + goto bad_node; + bsp = &nodes[bspnum]; + set_divline(bsp, /*OUT*/ &fdivline); + wpoly_init_0(&frontpoly); + wpoly_init_0(&backpoly); +#ifdef POLYTILE + polytile_remove(poly); +#endif + SplitPoly (&fdivline, poly, &frontpoly, &backpoly); +#ifdef POLYTILE + polytile_enter(&frontpoly); + polytile_enter(&backpoly); +#endif + +#ifdef DEBUG_HWBSP //debug - if (!backpoly) - nobackpoly++; + if (backpoly.numpts == 0) + nobackpoly_cnt++; +#endif // Recursively divide front space. - if (frontpoly) + if (frontpoly.numpts) { - WalkBSPNode(bsp->children[0], frontpoly, &bsp->children[0],bsp->bbox[0]); +#ifdef DEBUG_TRACE + if (trigger_trace) + { + CONS_Debug(DBG_RENDER, "BSP-FRONT %i:\n", frontpoly.id1); + } +#endif + + HWR_WalkBSPNode(bsp->children[0], &frontpoly, &bsp->children[0], bsp->bbox[0]); // copy child bbox - memcpy(bbox, bsp->bbox[0], 4*sizeof (fixed_t)); + memcpy(bbox, bsp->bbox[0], 4*sizeof(fixed_t)); } else - I_Error("WalkBSPNode: no front poly?"); + { +#ifdef DEBUG_TRACE + if (trigger_trace) + { + CONS_Debug(DBG_RENDER, "BSP-FRONT %i EMPTY:\n", backpoly.id1); + } +#endif + + // [WDJ] Having no front poly is as likely as no back poly, since + // logic in Split Poly was changed to check poly direction. + // I_Error ("HWR_WalkBSPNode: no front poly, bspnum= %d\n", bspnum); // I_SoftError + } // Recursively divide back space. - if (backpoly) + if (backpoly.numpts) { +#ifdef DEBUG_TRACE + if (trigger_trace) + { + CONS_Debug(DBG_RENDER, "BSP-BACK %i:\n", backpoly.id1); + } +#endif // Correct back bbox to include floor/ceiling convex polygon - WalkBSPNode(bsp->children[1], backpoly, &bsp->children[1], bsp->bbox[1]); + HWR_WalkBSPNode(bsp->children[1], &backpoly, &bsp->children[1], bsp->bbox[1]); // enlarge bbox with second child M_AddToBox(bbox, bsp->bbox[1][BOXLEFT ], - bsp->bbox[1][BOXTOP ]); + bsp->bbox[1][BOXTOP ]); M_AddToBox(bbox, bsp->bbox[1][BOXRIGHT ], - bsp->bbox[1][BOXBOTTOM]); + bsp->bbox[1][BOXBOTTOM]); } + else + { +#ifdef DEBUG_TRACE + if (trigger_trace) + { + CONS_Debug(DBG_RENDER, "BSP-BACK %i EMPTY:\n", backpoly.id1); + } +#endif + } + + wpoly_free(&backpoly); + wpoly_free(&frontpoly); + return; + +bad_subsector: + I_Error("HWR_WalkBSPNode : bad secnum %i, numsectors=%lu -1\n", + subsecnum, numsubsectors); + +bad_node: + // frontpoly and backpoly are empty, and were not init. + I_Error("HWR_WalkBSPNode : bad node num %i, numnodes=%lu -1\n", + bspnum, numnodes); } -// FIXME: use Z_Malloc() STATIC ? -void HWR_FreeExtraSubsectors(void) +static void HWR_Freepolysubsectors(void) { - if (extrasubsectors) - free(extrasubsectors); - extrasubsectors = NULL; + Z_Free(poly_subsectors); + poly_subsectors = NULL; } -#define MAXDIST 1.5f -// BP: can't move vertex: DON'T change polygon geometry! (convex) +#define MAXDIST (1.5f) +// BP: can't move vertex : DON'T change polygon geometry ! (convex) //#define MOVEVERTEX -static boolean PointInSeg(polyvertex_t *a,polyvertex_t *v1,polyvertex_t *v2) +// Is vertex va within the seg v1, v2 +static boolean PointInSeg(polyvertex_t* va, polyvertex_t* v1, polyvertex_t* v2) { - register float ax,ay,bx,by,cx,cy,d,norm; - register polyvertex_t *p; + register float ax, ay, bx, by, cx, cy, d, norm; - // check bbox of the seg first - if (v1->x > v2->x) + // check bbox of the seg first (without altering v1, v2) + if (v2->x > v1->x) { - p = v1; - v1 = v2; - v2 = p; + // check if x within seg box v1..v2 + if ((va->x + MAXDIST) < v1->x) goto not_in; + if ((va->x - MAXDIST) > v2->x) goto not_in; + } + else + { + // check if x within seg box v2..v1 + if ((va->x + MAXDIST) < v2->x) goto not_in; + if ((va->x - MAXDIST) > v1->x) goto not_in; } - if (a->x < v1->x-MAXDIST || a->x > v2->x+MAXDIST) - return false; - - if (v1->y > v2->y) + if (v2->y > v1->y) { - p = v1; - v1 = v2; - v2 = p; + // check if x within seg box v1..v2 + if ((va->y + MAXDIST) < v1->y) goto not_in; + if ((va->y - MAXDIST) > v2->y) goto not_in; + } + else + { + // check if x within seg box v2..v1 + if ((va->y + MAXDIST) < v2->y) goto not_in; + if ((va->y - MAXDIST) > v1->y) goto not_in; } - if (a->y < v1->y-MAXDIST || a->y > v2->y+MAXDIST) - return false; - // v1 = origine - ax= v2->x-v1->x; - ay= v2->y-v1->y; - norm = (float)hypot(ax, ay); - ax /= norm; - ay /= norm; - bx = a->x-v1->x; - by = a->y-v1->y; - //d = a.b - d =ax*bx+ay*by; + // v1 = origin + ax = v2->x - v1->x; + ay = v2->y - v1->y; + norm = hypotf(ax, ay); // length of seg +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wfloat-equal" + if (norm != 0) // yes, this can be exactly 0 +#pragma GCC diagnostic pop + { + ax /= norm; + ay /= norm; // unit vector along seg, v1->v2 + } + bx = va->x - v1->x; + by = va->y - v1->y; // vector v1->va + + // d = (a DOT b), (product of lengths * cosine( angle )) + d = ax*bx+ay*by; // bound of the seg if (d < 0 || d > norm) + { + // Also excludes some va within MAXDIST of v1 or v2 + goto not_in; + } + + // Cross product. + if (((by * ax) - (bx * ay)) <= 0) + { + // The vertex is to the rightside of the seg, so adding + // it to the polygon would worsen the crack. return false; - //c = d.1a-b + } + + // measure the error in vector bx,by as difference squared sum + //c = (d * unit_vector_seg) - b cx = ax*d-bx; cy = ay*d-by; #ifdef MOVEVERTEX - if (cx*cx+cy*cy <= MAXDIST*MAXDIST) + if (cx*cx+cy*cy<=MAXDIST*MAXDIST) { - // ajust a little the point position - a->x = ax*d+v1->x; - a->y = ay*d+v1->y; - // anyway the correction is not enouth + // adjust a little the point position + a->x=ax*d+v1->x; + a->y=ay*d+v1->y; + // anyway the correction is not enough return true; } return false; #else return cx*cx+cy*cy <= MAXDIST*MAXDIST; #endif +not_in: + return false; } -static INT32 numsplitpoly; +// [WDJ] The poly forming code has attempted to improve the polygons. +// Snapping of divide points to existing vertexes is one, and it may +// contribute to cracks in the floor tiling. Snapping to an existing +// vertex may pull the edge of a polygon away from the adjoining polygon. +// Inaccuracies in the division lines may also contribute to this. +// This code attempts to find such cracks and fix the polygons to cover them. -static void SearchSegInBSP(INT32 bspnum,polyvertex_t *p,poly_t *poly) +static int num_T_vertex_fixed; + +// A structure to pass in BSP recursion, reducing it to one parameter. +typedef struct { + fixed_t max_x, min_x, max_y, min_y; + wpoly_t * poly; // our poly + polyvertex_t * pt; // T-split vertex + polyvertex_t * before, * after; // shared vertex before and after it + int our_secnum, find_secnum; // sectors + int pt_index; +} split_T_t; + +// Dist 0.4999 cures HOM in Freedoom map09 +#define SEARCHSEG_VERTEX_DIST 0.4999f + +// Recursive descent in BSP. +static void SearchSegInBSP(INT32 bspnum, split_T_t * stp) { - poly_t *q; - INT32 j,k; + wpoly_t *wq = stp->poly; + polyvertex_t *pt = stp->pt; + size_t subsecnum; + INT32 numpts, i1, i2; - if (bspnum & NF_SUBSECTOR) + const fixed_t maxy = stp->max_y; + const fixed_t maxx = stp->max_x; + const fixed_t miny = stp->min_y; + + for (;;) { - if (bspnum != -1) - { - bspnum &= ~NF_SUBSECTOR; - q = extrasubsectors[bspnum].planepoly; - if (poly == q || !q) - return; - for (j = 0; j < q->numpts; j++) - { - k = j+1; - if (k == q->numpts) k = 0; - if (!SameVertice(p, &q->pts[j]) - && !SameVertice(p, &q->pts[k]) - && PointInSeg(p, &q->pts[j], - &q->pts[k])) - { - poly_t *newpoly = HWR_AllocPoly(q->numpts+1); - INT32 n; + if (bspnum & NF_SUBSECTOR) + goto got_subsector; - for (n = 0; n <= j; n++) - newpoly->pts[n] = q->pts[n]; - newpoly->pts[k] = *p; - for (n = k+1; n < newpoly->numpts; n++) - newpoly->pts[n] = q->pts[n-1]; - numsplitpoly++; - extrasubsectors[bspnum].planepoly = - newpoly; - HWR_FreePoly(q); - return; - } - } - } - return; + const node_t *node = &nodes[bspnum]; + const fixed_t *bbox0 = nodes[bspnum].bbox[0]; + + const boolean left = + ((bbox0[BOXBOTTOM] <= maxy) & + (bbox0[BOXTOP] >= miny) & + (bbox0[BOXLEFT] <= maxx) & + (bbox0[BOXRIGHT] >= miny)); + + // Not a subsector, visit left and right children. + if (left) + SearchSegInBSP(node->children[0], stp); + + const fixed_t *bbox1 = nodes[bspnum].bbox[1]; + + const boolean right = + ((bbox1[BOXBOTTOM] <= maxy) & + (bbox1[BOXTOP] >= miny) & + (bbox1[BOXLEFT] <= maxx) & + (bbox1[BOXRIGHT] >= miny)); + + if (!right) + break; + + // Tail recursion within loop. + bspnum = node->children[1]; } - if ((FIXED_TO_FLOAT(nodes[bspnum].bbox[0][BOXBOTTOM])-MAXDIST <= p->y) && - (FIXED_TO_FLOAT(nodes[bspnum].bbox[0][BOXTOP ])+MAXDIST >= p->y) && - (FIXED_TO_FLOAT(nodes[bspnum].bbox[0][BOXLEFT ])-MAXDIST <= p->x) && - (FIXED_TO_FLOAT(nodes[bspnum].bbox[0][BOXRIGHT ])+MAXDIST >= p->x)) - SearchSegInBSP(nodes[bspnum].children[0],p,poly); + return; - if ((FIXED_TO_FLOAT(nodes[bspnum].bbox[1][BOXBOTTOM])-MAXDIST <= p->y) && - (FIXED_TO_FLOAT(nodes[bspnum].bbox[1][BOXTOP ])+MAXDIST >= p->y) && - (FIXED_TO_FLOAT(nodes[bspnum].bbox[1][BOXLEFT ])-MAXDIST <= p->x) && - (FIXED_TO_FLOAT(nodes[bspnum].bbox[1][BOXRIGHT ])+MAXDIST >= p->x)) - SearchSegInBSP(nodes[bspnum].children[1],p,poly); +got_subsector: + subsecnum = bspnum & ~NF_SUBSECTOR; + if (subsecnum >= numsubsectors) + return; + + // For every subsector polygon different than poly + wq = & wpoly_subsectors[subsecnum]; + if (wq == stp->poly || !wq) + return; + + numpts = wq->numpts; + if (numpts == 0) + return; + + // For all the vertex. + for (i1 = 0; i1 < numpts; i1++) + { + if (wq->ppts[i1] == pt) + continue; + + i2 = i1+1; + + if (i2 == numpts) + i2 = 0; + + if (wq->ppts[i2] == pt) + continue; + + if (PointInSeg(pt, wq->ppts[i1], wq->ppts[i2])) + { + goto add_pt; + } + } + // May have to search more than one polygon to find correct neighbor polygon. + return; + +add_pt: + // Insert the vertex pt into the polygon of the bsp subsector. + wpoly_insert_vert(pt, i2, /*INOUT*/ wq); + num_T_vertex_fixed++; + //stp->max_y = - 0x7ffffff0; // exit all the way + return; } // search for T-intersection problem -// BP : It can be mush more faster doing this at the same time of the splitpoly -// but we must use a different structure : polygone pointing on segs -// segs pointing on polygone and on vertex (too mush complicated, well not -// realy but i am soo lasy), the methode discibed is also better for segs presition -static INT32 SolveTProblem(void) -{ - poly_t *p; - INT32 i; - size_t l; +// BP : It can be much more faster doing this at the same time of the splitpoly +// but we must use a different structure : polygon pointing on segs +// segs pointing on polygon and on vertex (too much complicated, well not +// really but I am soo lazy), the method described is also better for segs precision - if (cv_glsolvetjoin.value == 0) - return 0; +static void SolveTProblem(void) +{ + split_T_t splitt; // parameter to search + wpoly_t *wp; + INT32 numpts, i, j; + size_t ssnum; + + if (!cv_glsolvetjoin.value) + return; CONS_Debug(DBG_RENDER, "Solving T-joins. This may take a while. Please wait...\n"); -#ifdef HWR_LOADING_SCREEN - CON_Drawer(); //let the user know what we are doing - I_FinishUpdate(); // page flip or blit buffer -#endif - numsplitpoly = 0; + num_T_vertex_fixed = 0; - for (l = 0; l < addsubsector; l++) + // For every subsector + for (ssnum = 0; ssnum < num_poly_subsector; ssnum++) { - p = extrasubsectors[l].planepoly; - if (p) - for (i = 0; i < p->numpts; i++) - SearchSegInBSP((INT32)numnodes-1, &p->pts[i], p); + wp = & wpoly_subsectors[ssnum]; + if (wp->numpts == 0) + continue; + + splitt.poly = wp; + numpts = wp->numpts; + + // For all vertex in the subsector + for (i = 0; i < numpts; i++) + { +#ifdef DEBUG_HWBSP + if (wp->ppts == NULL) + { + CONS_Debug(DBG_RENDER, "SolveT: NULL vertex, subsector= %d\n", ssnum); + } +#endif + // No need to process polyvertex from the level map. + if (in_poly_vert(wp->ppts[i])) + continue; + + // This is a vertex added by a split. + splitt.pt_index = i; + splitt.pt = wp->ppts[i]; + splitt.max_x = (fixed_t)((wp->ppts[i]->x + MAXDIST) * 0x10000); + splitt.min_x = (fixed_t)((wp->ppts[i]->x - MAXDIST) * 0x10000); + splitt.max_y = (fixed_t)((wp->ppts[i]->y + MAXDIST) * 0x10000); + splitt.min_y = (fixed_t)((wp->ppts[i]->y - MAXDIST) * 0x10000); + + j = i - 1; + + if (j < 0) + j += numpts; + + splitt.before = wp->ppts[j]; + + j = i + 1; + + if (j >= numpts) + j -= numpts; + + splitt.after = wp->ppts[j]; + + // Check added polyvertex due to SplitPoly. + SearchSegInBSP(numnodes-1, & splitt); + } } - //CONS_Debug(DBG_RENDER, "numsplitpoly %d\n", numsplitpoly); - return numsplitpoly; +#ifdef DEBUG_HWBSP + CONS_Debug(DBG_RENDER, "DEBUG: SolveT: div polygon line= %d\n", num_T_vertex_fixed); +#endif } -#define NEARDIST (0.75f) -#define MYMAX (10000000000000.0f) +// lug: dont modify our actual sectors, that´ll break maps and causes desynchs! +//#define CHANGESUBSEC -/* Adjust true segs (from the segs lump) to be exactely the same as - * plane polygone segs - * This also convert fixed_t point of segs in float (in moste case - * it share the same vertice - */ +#ifdef CHANGESUBSEC +// [WDJ] Have a subsector poly with a suspect sector. +// Return the correct sector for the subsector poly. +static sector_t *find_poly_sector(wpoly_t *ssp) +{ + // Examine every linedef for the closest along the x or y axis. + // Use this linedef to assign the sector to the poly subsector. + + // There should be no two-sided linedefs actually within the subsector. + // If there were any one-sided linedefs within the subsector, they would + // have been segs, and would have decided the issue already. + polyvertex_t ap; // avg of subsector points + line_t *best_lp = NULL; + fixed_t best_dd = 0x7fffffff; + fixed_t px, py; + INT32 j; + size_t k; + + // Find an average point, not on a line, within the sector. + // If it is on the poly boundary, it can confuse the linedef exclusion tests. + ap.x = ap.y = 0.0; + for (j = 0; j < ssp->numpts; j++) + { + ap.x += ssp->ppts[j]->x; + ap.y += ssp->ppts[j]->y; + } + // Average of poly points. + ap.x /= ssp->numpts; + ap.y /= ssp->numpts; + px = (int)(ap.x * 0x10000); + py = (int)(ap.y * 0x10000); + +#ifdef DEBUG_FPS + CONS_Debug(DBG_RENDER, "Find Poly Sector: (%6.2f,%6.2f)\n", ap.x, ap.y); +#endif + + // Find closest linedef to the test point, along x and y axis. + // Testing only one axis would work. + // But a linedef may be much closer along the other axis, so we test along both X and Y. + for (k = 0; k < numlines; k++) + { + line_t *lp = & lines[k]; + + if (lp->frontsector == lp->backsector) + continue; // self-ref lines lie. + + // Only consider linedef that bracket the test point. + fixed_t dx1 = px - lp->v1->x; + fixed_t dy1 = py - lp->v1->y; + fixed_t dx2 = px - lp->v2->x; + fixed_t dy2 = py - lp->v2->y; + + // (dx1 XOR dx2) < 0 means that one was < 0 and the other was > 0, + // which means it bracketed the point px,py. + + // Eqn of line: x = x1 + a * dx, y = y1 + a * dy + // At px: a = (px - x1) / dx + // dd = abs( (py - y1) - ( (px - x1) * dy / dx)) + if (((dx1 ^ dx2) < 0) && (lp->dx != 0)) // bracket px, and line not vert. + { + // Distance to line, measured along x-axis. + // This calc has a tendency to overflow, so use INT64. + INT64 dy3 = ((INT64)dx1) * lp->dy / lp->dx; +#ifdef DEBUG_FPS + CONS_Debug(DBG_RENDER, "FPS X: line= %i (%6.2f,%6.2f) to (%6.2f,%6.2f) dx,dy=(%6.2f,%6.2f) \n", + k, FIXED_TO_FLOAT(lp->v1->x), FIXED_TO_FLOAT(lp->v1->y), FIXED_TO_FLOAT(lp->v2->x), FIXED_TO_FLOAT(lp->v2->y), + FIXED_TO_FLOAT(lp->dx), FIXED_TO_FLOAT(lp->dy)); +#endif + if ((dy3 > INT32_MIN) && (dy3 < INT32_MAX)) // within fixed_t range + { + fixed_t dd = abs(dy1 - (fixed_t)dy3); +#ifdef DEBUG_FPS + CONS_Debug(DBG_RENDER, "X dd=%6.2f\n", FIXED_TO_FLOAT(dd)); +#endif + if (dd < best_dd ) + { + best_dd = dd; + best_lp = lp; +#ifdef DEBUG_FPS + CONS_Debug(DBG_RENDER, "BEST=%i X dist=%i\n", k, dd>>16); +#endif + } + } + } + + if (((dy1 ^ dy2) < 0) && (lp->dy != 0)) // bracket py, and line not horz. + { + // Distance to line, measured along y-axis. + // This calc has a tendency to overflow, so use INT64. + INT64 dx3 = ((INT64)dy1) * lp->dx / lp->dy; +#ifdef DEBUG_FPS + CONS_Debug(DBG_RENDER, "FPS Y: line= %i (%6.2f,%6.2f) to (%6.2f,%6.2f) dx,dy=(%6.2f,%6.2f) \n", + k, FIXED_TO_FLOAT(lp->v1->x), FIXED_TO_FLOAT(lp->v1->y), FIXED_TO_FLOAT(lp->v2->x), FIXED_TO_FLOAT(lp->v2->y), + FIXED_TO_FLOAT(lp->dx), FIXED_TO_FLOAT(lp->dy)); +#endif + if ((dx3 > INT32_MIN) && (dx3 < INT32_MAX)) // within fixed_t range + { + fixed_t dd = abs(dx1 - (fixed_t)dx3); +#ifdef DEBUG_FPS + CONS_Debug(DBG_RENDER, "Y dd=%6.2f\n", FIXED_TO_FLOAT(dd)); +#endif + if (dd < best_dd ) + { + best_dd = dd; + best_lp = lp; +#ifdef DEBUG_FPS + CONS_Debug(DBG_RENDER, "BEST=%i Y dist=%i\n", k, dd>>16); +#endif + } + } + } + } + + if (best_lp == NULL) + return NULL; + + // cross product with best_lp, to detect ap on rightside + double crpd = ((((double)(ap.x)) - FIXED_TO_FLOAT(best_lp->v1->x)) * FIXED_TO_FLOAT(best_lp->dy)) + - ((((double)(ap.y)) - FIXED_TO_FLOAT(best_lp->v1->y)) * FIXED_TO_FLOAT(best_lp->dx)); + + sector_t *fnd_sector = (crpd >= 0) + ? best_lp->frontsector // rightside of linedef + : best_lp->backsector; +#ifdef DEBUG_FPS + CONS_Debug(DBG_RENDER, "crpd=%6.2f sector=%i\n", crpd, fnd_sector - sectors); +#endif + return fnd_sector; +} +#endif + +#define VERTEX_NEAR_DIST (0.75f) +// Only needs to be reasonably larger than VERTEX_NEAR_DIST. +#define INITIAL_MAX (10000000000000.0f) +#define SEG_SAME_VERT (0.5f) + +// Adds polyvertex_t references to the segs. +// [WDJ] 2013/12 Removed writes of polyvertex_t* to vertex_t*, it now has its +// own ptrs. Fewer reverse conversions are needed. static void AdjustSegs(void) { - size_t i, count; +#ifdef DEBUG_HWBSP + int missed_seg_cnt = 0; + int fixed_segsec_cnt = 0; + int lost_segsec_cnt = 0; +#endif + INT16 segcount; + size_t ssnum; INT32 j; - seg_t *lseg; - poly_t *p; +#ifdef CHANGESUBSEC + sector_t *ss_sector, *poly_sector, *lseg_sector; +#endif + seg_t* lseg; + wpoly_t *wp; INT32 v1found = 0, v2found = 0; float nearv1, nearv2; - for (i = 0; i < numsubsectors; i++) + // for all segs in all sectors + for (ssnum = 0; ssnum < numsubsectors; ssnum++) { - count = subsectors[i].numlines; - lseg = &segs[subsectors[i].firstline]; - p = extrasubsectors[i].planepoly; - //if (!p) - //continue; - for (; count--; lseg++) + wp = & wpoly_subsectors[ssnum]; + if (wp->numpts == 0) + continue; + + segcount = subsectors[ssnum].numlines; +#ifdef CHANGESUBSEC + ss_sector = subsectors[ssnum].sector; +#endif + lseg = &segs[subsectors[ssnum].firstline]; +#ifdef CHANGESUBSEC + poly_sector = NULL; +#endif + for (; segcount--; lseg++) { - float distv1,distv2,tmp; - nearv1 = nearv2 = MYMAX; + polyvertex_t sv1, sv2; // seg v1, v2 + float distv1, distv2, tmp; // Don't touch polyobject segs. We'll compensate // for this when we go about drawing them. if (lseg->polyseg) continue; - if (p) { - for (j = 0; j < p->numpts; j++) + const line_t *line = lseg->linedef; + + if (!line) + continue; + + if (line->sidenum[1] != 0xffff) + { + if (sides[line->sidenum[0]].sector == sides[line->sidenum[1]].sector) { - distv1 = p->pts[j].x - FIXED_TO_FLOAT(lseg->v1->x); - tmp = p->pts[j].y - FIXED_TO_FLOAT(lseg->v1->y); - distv1 = distv1*distv1+tmp*tmp; - if (distv1 <= nearv1) - { - v1found = j; - nearv1 = distv1; - } - // the same with v2 - distv2 = p->pts[j].x - FIXED_TO_FLOAT(lseg->v2->x); - tmp = p->pts[j].y - FIXED_TO_FLOAT(lseg->v2->y); - distv2 = distv2*distv2+tmp*tmp; - if (distv2 <= nearv2) - { - v2found = j; - nearv2 = distv2; - } + // Segs that are self-ref linedef do not cutout the subsector. +#ifdef DEBUG_HWBSP + CONS_Debug(DBG_RENDER, "AdjustSegs: self ref line %i\n", line - lines); +#endif + continue; } } - if (p && nearv1 <= NEARDIST*NEARDIST) - // share vertice with segs - lseg->pv1 = &(p->pts[v1found]); + +#ifdef CHANGESUBSEC + if (line->sidenum[lseg->side] != 0xffff) + { + // Get the sector from the seg. + lseg_sector = sides[line->sidenum[lseg->side]].sector; +#ifdef DEBUG_HWBSP + int secnum = lseg_sector - sectors; + if (lseg_sector != ss_sector) + CONS_Debug(DBG_RENDER, "AdjustSegs: seg line sector = %i, subsector sector = %i\n", + secnum, ss_sector - sectors); + if (poly_sector && (lseg_sector != poly_sector)) + CONS_Debug(DBG_RENDER, "AdjustSegs: seg line sector = %i, and %i\n", + secnum, poly_sector - sectors); +#endif + poly_sector = lseg_sector; + } +#endif + + sv1.x = FIXED_TO_FLOAT(lseg->v1->x); + sv1.y = FIXED_TO_FLOAT(lseg->v1->y); + sv2.x = FIXED_TO_FLOAT(lseg->v2->x); + sv2.y = FIXED_TO_FLOAT(lseg->v2->y); + + nearv1 = nearv2 = INITIAL_MAX; + // find nearest existing poly pts to seg v1, v2 + for (j = 0; j < wp->numpts; j++) + { + distv1 = wp->ppts[j]->x - sv1.x; + tmp = wp->ppts[j]->y - sv1.y; + distv1 = distv1*distv1 + tmp*tmp; + + if (distv1 <= nearv1) + { + v1found = j; + nearv1 = distv1; + } + + // the same with v2 + distv2 = wp->ppts[j]->x - sv2.x; + tmp = wp->ppts[j]->y - sv2.y; + distv2 = distv2*distv2 + tmp*tmp; + + if (distv2 <= nearv2) + { + v2found = j; + nearv2 = distv2; + } + } + + // close enough to be considered the same ? + if (nearv1 <= VERTEX_NEAR_DIST*VERTEX_NEAR_DIST) + { + // share vertex with segs + lseg->pv1 = wp->ppts[v1found]; + } else { // BP: here we can do better, using PointInSeg and compute - // the right point position also split a polygone side to - // solve a T-intersection, but too mush work + // the right point position also split a polygon side to + // solve a T-intersection, but too much work - // convert fixed vertex to float vertex - polyvertex_t *pv = HWR_AllocVertex(); - pv->x = FIXED_TO_FLOAT(lseg->v1->x); - pv->y = FIXED_TO_FLOAT(lseg->v1->y); - lseg->pv1 = pv; + lseg->pv1 = store_polyvertex(&sv1, SEG_SAME_VERT); + } + if (nearv2 <= VERTEX_NEAR_DIST*VERTEX_NEAR_DIST) + { + lseg->pv2 = wp->ppts[v2found]; } - if (p && nearv2 <= NEARDIST*NEARDIST) - lseg->pv2 = &(p->pts[v2found]); else { - polyvertex_t *pv = HWR_AllocVertex(); - pv->x = FIXED_TO_FLOAT(lseg->v2->x); - pv->y = FIXED_TO_FLOAT(lseg->v2->y); - lseg->pv2 = pv; + lseg->pv2 = store_polyvertex(&sv2, SEG_SAME_VERT); } // recompute length { - float x,y; - x = ((polyvertex_t *)lseg->pv2)->x - ((polyvertex_t *)lseg->pv1)->x - + FIXED_TO_FLOAT(FRACUNIT/2); - y = ((polyvertex_t *)lseg->pv2)->y - ((polyvertex_t *)lseg->pv1)->y - + FIXED_TO_FLOAT(FRACUNIT/2); - lseg->flength = (float)hypot(x, y); + const polyvertex_t *pv1 = (polyvertex_t *)lseg->pv1; + const polyvertex_t *pv2 = (polyvertex_t *)lseg->pv2; + + // [WDJ] FIXED_TO_FLOAT_MULT used to add 1/2 of lsb of fixed_t fraction. + float x = pv2->x - pv1->x + (0.5*FIXED_TO_FLOAT_MULT); + float y = pv2->y - pv1->y + (0.5*FIXED_TO_FLOAT_MULT); + lseg->flength = hypotf(x, y); + // BP: debug see this kind of segs - //if (nearv2 > NEARDIST*NEARDIST || nearv1 > NEARDIST*NEARDIST) - // lseg->length = 1; + //if (nearv2>VERTEX_NEAR_DIST*VERTEX_NEAR_DIST || nearv1>VERTEX_NEAR_DIST*VERTEX_NEAR_DIST) + //lseg->flength=1; } } + +#ifdef CHANGESUBSEC + // Fix bad subsector sector references. + if (poly_sector == NULL) + { + poly_sector = find_poly_sector(wp); +#ifdef DEBUG_HWBSP + lost_segsec_cnt++; +#endif + } + + if (poly_sector && (poly_sector != ss_sector)) + { + subsectors[ssnum].sector = poly_sector; +#ifdef DEBUG_HWBSP + fixed_segsec_cnt++; +#endif + } +#endif + } + + // check for missed segs, not in any polygon + for (j = 0; (size_t)j < numsegs; j++) + { + lseg = &segs[j]; + + // Don't touch polyobject segs. We'll compensate + // for this when we go about drawing them. + if (lseg->polyseg) + continue; + + if (!(lseg->pv1 && lseg->pv2)) + { + CONS_Debug(DBG_RENDER, "Seg %i, not in any polygon.\n", j); + } +#ifdef DEBUG_HWBSP + if ((!lseg->pv1) || (!lseg->pv2)) + missed_seg_cnt++; +#endif + if (!lseg->pv1) + { + lseg->pv1 = store_vertex(lseg->v1, SEG_SAME_VERT); + } + + if (!lseg->pv2) + { + lseg->pv2 = store_vertex(lseg->v2, SEG_SAME_VERT); + } + } +#ifdef DEBUG_HWBSP + + CONS_Debug(DBG_RENDER, "DEBUG: Lost seg sector cnt = %i\n", lost_segsec_cnt); + CONS_Debug(DBG_RENDER, "DEBUG: Fixed seg sector cnt = %i\n", fixed_segsec_cnt); + CONS_Debug(DBG_RENDER, "DEBUG: Missed seg vertex cnt = %i\n", missed_seg_cnt); +#endif +} + +// Generate drawing polygons from wpoly_t versions. +static void finalize_polygons(void) +{ + wpoly_t *wpoly; + poly_t *dpoly; // drawing poly + polyvertex_t *pv; + size_t ssnum; + INT16 ps; + + // For all segs in all sectors. + for (ssnum = 0; ssnum < numsubsectors; ssnum++) + { + wpoly = & wpoly_subsectors[ssnum]; + +#ifdef DEBUG_TRACE + if (trigger_subsector == 0xFFFFFFF2 || trigger_subsector == ssnum) + wpoly_dump( "Finalize:", wpoly); +#endif + + // Generate poly in poly_t format. + // Vertex in wpoly_t are ptr, but in poly_t they are a copy of the vertex. + dpoly = HWR_AllocPoly(wpoly->numpts); + poly_subsectors[ssnum].planepoly = dpoly; + pv = dpoly->pts; + + for ( ps = 0; ps < wpoly->numpts; ps++ ) + { + *pv++ = *(wpoly->ppts[ps]); // copy of each vertex + } } } - -// call this routine after the BSP of a Doom wad file is loaded, -// and it will generate all the convex polys for the hardware renderer +// Call this routine after the BSP of a Doom wad file is loaded, +// and it will generate all the convex polys for the hardware renderer. +// Called from P_SetupLevel void HWR_CreatePlanePolygons(INT32 bspnum) { - poly_t *rootp; - polyvertex_t *rootpv; + wpoly_t rootp; + polyvertex_t** rootpv; size_t i; fixed_t rootbbox[4]; + (void)bspnum; + CONS_Debug(DBG_RENDER, "Creating polygons, please wait...\n"); + #ifdef HWR_LOADING_SCREEN ls_count = ls_percent = 0; // reset the loading status CON_Drawer(); //let the user know what we are doing I_FinishUpdate(); // page flip or blit buffer #endif - HWR_ClearPolys(); - + // Enter all vertexes into the root bounding box. // find min/max boundaries of map - //CONS_Debug(DBG_RENDER, "Looking for boundaries of map...\n"); + CONS_Debug(DBG_RENDER, "Looking for boundaries of map...\n"); M_ClearBox(rootbbox); - for (i = 0;i < numvertexes; i++) + for (i = 0; i < numvertexes; i++) M_AddToBox(rootbbox, vertexes[i].x, vertexes[i].y); - //CONS_Debug(DBG_RENDER, "Generating subsector polygons... %d subsectors\n", numsubsectors); + CONS_Debug(DBG_RENDER, "Generating subsector polygons... %lu subsectors\n", numsubsectors); - HWR_FreeExtraSubsectors(); // allocate extra data for each subsector present in map - totsubsectors = numsubsectors + NEWSUBSECTORS; - extrasubsectors = calloc(totsubsectors, sizeof (*extrasubsectors)); - if (extrasubsectors == NULL) - I_Error("couldn't malloc extrasubsectors totsubsectors %s\n", sizeu1(totsubsectors)); + num_alloc_poly_subsector = numsubsectors + NUM_EXTRA_SUBSECTORS; + poly_subsectors = Z_Calloc(sizeof(poly_subsector_t) * num_alloc_poly_subsector, PU_STATIC, NULL); // set all data in to 0 or NULL !!! + + wpoly_subsectors = Z_Calloc(sizeof(wpoly_t) * num_alloc_poly_subsector, PU_HWRPLANE, NULL); // allocate table for back to front drawing of subsectors - /*gl_drawsubsectors = (INT16 *)malloc(sizeof (*gl_drawsubsectors) * totsubsectors); - if (!gl_drawsubsectors) - I_Error("couldn't malloc gl_drawsubsectors\n");*/ + /*gr_drawsubsectors = (short*)malloc(sizeof(*gr_drawsubsectors) * num_alloc_poly_subsector); + if (!gr_drawsubsectors) + I_Error("couldn't malloc gr_drawsubsectors\n");*/ + + // The level map polyvertexes + create_poly_vert(); // number of the first new subsector that might be added - addsubsector = numsubsectors; + num_poly_subsector = numsubsectors; // construct the initial convex poly that encloses the full map - rootp = HWR_AllocPoly(4); - rootpv = rootp->pts; + wpoly_init_alloc(4, &rootp); // alloc space for 4 pts +#ifdef DEBUG_TRACE + rootp.id1 = poly_id++; + rootp.id2 = 0; + rootp.id3 = 0; +#endif + rootp.numpts = 4; + rootpv = rootp.ppts; + rootpv[0] = new_polyvertex(); + rootpv[1] = new_polyvertex(); + rootpv[2] = new_polyvertex(); + rootpv[3] = new_polyvertex(); + // clockwise polygon + // 0=lower_left, 1=upper_left, 2=upper_right, 3=lower_right + rootpv[0]->x = rootpv[1]->x = FIXED_TO_FLOAT(rootbbox[BOXLEFT ]); + rootpv[2]->x = rootpv[3]->x = FIXED_TO_FLOAT(rootbbox[BOXRIGHT ]); + rootpv[1]->y = rootpv[2]->y = FIXED_TO_FLOAT(rootbbox[BOXTOP ]); + rootpv[0]->y = rootpv[3]->y = FIXED_TO_FLOAT(rootbbox[BOXBOTTOM]); - rootpv->x = FIXED_TO_FLOAT(rootbbox[BOXLEFT ]); - rootpv->y = FIXED_TO_FLOAT(rootbbox[BOXBOTTOM]); //lr - rootpv++; - rootpv->x = FIXED_TO_FLOAT(rootbbox[BOXLEFT ]); - rootpv->y = FIXED_TO_FLOAT(rootbbox[BOXTOP ]); //ur - rootpv++; - rootpv->x = FIXED_TO_FLOAT(rootbbox[BOXRIGHT ]); - rootpv->y = FIXED_TO_FLOAT(rootbbox[BOXTOP ]); //ul - rootpv++; - rootpv->x = FIXED_TO_FLOAT(rootbbox[BOXRIGHT ]); - rootpv->y = FIXED_TO_FLOAT(rootbbox[BOXBOTTOM]); //ll - rootpv++; + // start at head node of bsp tree + // [WDJ] Intercept degenerate case, so BSP node is never -1. + HWR_WalkBSPNode(((numnodes > 0) ? numnodes-1 + : (0 | NF_SUBSECTOR)), // Degenerate, sector 0 + &rootp, NULL, rootbbox); - WalkBSPNode(bspnum, rootp, NULL,rootbbox); + SolveTProblem(); - i = SolveTProblem(); - //CONS_Debug(DBG_RENDER, "%d point divides a polygon line\n",i); AdjustSegs(); +#ifdef POLYTILE + polytile_clean(); +#endif + finalize_polygons(); // wpoly_t to drawing polygons + Z_Free(wpoly_subsectors); + wpoly_subsectors = NULL; + +#ifdef DEBUG_HWBSP //debug debug.. - //if (nobackpoly) - // CONS_Debug(DBG_RENDER, "no back polygon %u times\n",nobackpoly); - //"(should happen only with the deep water trick)" - //if (skipcut) - // CONS_Debug(DBG_RENDER, "%u cuts were skipped because of only one point\n",skipcut); + if (nobackpoly_cnt) + { + // should happen only with the deep water trick + CONS_Debug(DBG_RENDER, "DEBUG: no back polygon cnt= %d\n", nobackpoly_cnt); + } - //CONS_Debug(DBG_RENDER, "done: %u total subsector convex polygons\n", totalsubsecpolys); + if (skipcut_cnt) + CONS_Debug(DBG_RENDER, "DEBUG: cuts skipped because of only one point= %d\n", skipcut_cnt); + + CONS_Debug(DBG_RENDER, "DEBUG: total subsector convex polygons= %d\n", total_subsecpoly_cnt); +#endif } #endif //HWRENDER diff --git a/src/hardware/hw_plane.c b/src/hardware/hw_plane.c index 179895f1d..f4c862e5b 100644 --- a/src/hardware/hw_plane.c +++ b/src/hardware/hw_plane.c @@ -40,7 +40,7 @@ static FUINT HWR_CalcSlopeLight(FUINT lightnum, pslope_t *slope, const sector_t // -----------------+ // HWR_RenderPlane : Render a floor or ceiling convex polygon // -----------------+ -void HWR_RenderPlane(subsector_t *subsector, extrasubsector_t *xsub, boolean isceiling, fixed_t fixedheight, FBITFIELD PolyFlags, INT32 lightlevel, levelflat_t *levelflat, sector_t *FOFsector, UINT8 alpha, extracolormap_t *planecolormap) +void HWR_RenderPlane(subsector_t *subsector, poly_subsector_t *xsub, boolean isceiling, fixed_t fixedheight, FBITFIELD PolyFlags, INT32 lightlevel, levelflat_t *levelflat, sector_t *FOFsector, UINT8 alpha, extracolormap_t *planecolormap) { polyvertex_t * pv; float height; //constant y for all points on the convex flat polygon diff --git a/src/p_setup.c b/src/p_setup.c index 1465e718f..2631db202 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -9051,21 +9051,20 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) // Load the ccheckpoints and waypoints please! if (gametyperules & GTR_CIRCUIT && gamestate != GS_TITLESCREEN) { - - if ((K_SetupWaypointList() == false)) + if ((K_SetupWaypointList() == false)) + { + if (!K_UsingLegacyCheckpoints()) { - if (!K_UsingLegacyCheckpoints()) - { - CONS_Alert(CONS_ERROR, "Waypoints were not able to be setup and legacy checkpoints do not exist! Player positions will not work correctly.\n"); - } + CONS_Alert(CONS_ERROR, "Waypoints were not able to be setup and legacy checkpoints do not exist! Player positions will not work correctly.\n"); } + } } #ifdef HWRENDER // not win32 only 19990829 by Kin gl_maploaded = false; - // Lactozilla: Free extrasubsectors regardless of renderer. - HWR_FreeExtraSubsectors(); + // Lactozilla: Free poly_subsectors regardless of renderer. + HWR_FreePolyPool(); // Create plane polygons. if (rendermode == render_opengl) From 0d8203d5c1d85f0dbfa2882c39c21174571ad657 Mon Sep 17 00:00:00 2001 From: Alug Date: Mon, 9 Mar 2026 19:48:06 +0100 Subject: [PATCH 15/18] guard against -1 bspnum in SearchSegInBSP technically we will never reach this case since the game will error out on nodeless maps anyways tho this is just for correctness --- src/hardware/hw_map.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/hardware/hw_map.c b/src/hardware/hw_map.c index ca6051b65..3d5b89a52 100644 --- a/src/hardware/hw_map.c +++ b/src/hardware/hw_map.c @@ -2056,7 +2056,7 @@ static void HWR_WalkBSPNode(INT32 bspnum, wpoly_t* poly, UINT16 *leafnode, fixed wpoly_t frontpoly; fdivline_t fdivline; polyvertex_t *pt; - INT32 subsecnum; // subsector index + size_t subsecnum; // subsector index INT32 i; (void)leafnode; // unused @@ -2073,7 +2073,7 @@ static void HWR_WalkBSPNode(INT32 bspnum, wpoly_t* poly, UINT16 *leafnode, fixed { // Subsector leaf: bspnum is a subsector number. subsecnum = bspnum & ~NF_SUBSECTOR; - if ((size_t)subsecnum >= numsubsectors) + if (subsecnum >= numsubsectors) goto bad_subsector; #ifdef DEBUG_TRACE @@ -2121,7 +2121,7 @@ static void HWR_WalkBSPNode(INT32 bspnum, wpoly_t* poly, UINT16 *leafnode, fixed } // Node reference: bspnum is another node of the tree. - if ((size_t)bspnum >= numnodes) + if (bspnum >= (INT32)numnodes) goto bad_node; bsp = &nodes[bspnum]; @@ -2444,7 +2444,7 @@ static void SolveTProblem(void) // For every subsector for (ssnum = 0; ssnum < num_poly_subsector; ssnum++) { - wp = & wpoly_subsectors[ssnum]; + wp = &wpoly_subsectors[ssnum]; if (wp->numpts == 0) continue; @@ -2487,7 +2487,8 @@ static void SolveTProblem(void) splitt.after = wp->ppts[j]; // Check added polyvertex due to SplitPoly. - SearchSegInBSP(numnodes-1, & splitt); + // lug: handle "degenerate" case so bspnum is never -1 here either + SearchSegInBSP(((numnodes > 0) ? numnodes-1 : (0 | NF_SUBSECTOR)), &splitt); } } #ifdef DEBUG_HWBSP From 7b3e9200298038df00773a251034c3799657f903 Mon Sep 17 00:00:00 2001 From: Alug Date: Mon, 9 Mar 2026 19:54:46 +0100 Subject: [PATCH 16/18] fix wrong format specifier subsecnum be size_t oop --- src/hardware/hw_map.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hardware/hw_map.c b/src/hardware/hw_map.c index 3d5b89a52..7398521e2 100644 --- a/src/hardware/hw_map.c +++ b/src/hardware/hw_map.c @@ -2205,7 +2205,7 @@ static void HWR_WalkBSPNode(INT32 bspnum, wpoly_t* poly, UINT16 *leafnode, fixed return; bad_subsector: - I_Error("HWR_WalkBSPNode : bad secnum %i, numsectors=%lu -1\n", + I_Error("HWR_WalkBSPNode : bad secnum %lu, numsectors=%lu -1\n", subsecnum, numsubsectors); bad_node: From 03b2f23bf7805d71544e91bc0753b5fb821cc912 Mon Sep 17 00:00:00 2001 From: Alug Date: Mon, 9 Mar 2026 23:15:58 +0100 Subject: [PATCH 17/18] fixes for the new ogl map preprocessor -fix faulty boundingbox check in SearchSegInBSP -fix SplitPoly and CutOutSubsecPoly potentially using uninitialized div points -fix a few broken debug prints -remove code for non ZPLANALLOC path, which was broken anyways -prevent potentially polytile_free dangling pointer -do some more style cleanup -remove unused struct members from split_T_t for #226 --- src/hardware/hw_map.c | 204 ++++++++++++++++-------------------------- 1 file changed, 78 insertions(+), 126 deletions(-) diff --git a/src/hardware/hw_map.c b/src/hardware/hw_map.c index 7398521e2..f70457ebf 100644 --- a/src/hardware/hw_map.c +++ b/src/hardware/hw_map.c @@ -37,9 +37,6 @@ static int trigger_subsector = 0xFFFFFFFF; static byte trigger_trace = 0; #endif -// Allocate poly from ZAlloc. -#define ZPLANALLOC - #define POLYTILE @@ -55,7 +52,7 @@ static byte trigger_trace = 0; polyvertex_t* poly_vert = NULL; // Create float poly vert from level map vertexes. -// These are freed by Z_Free( PU_HWRPLANE ). +// These are freed by Z_Free(PU_HWRPLANE). static void create_poly_vert(void) { polyvertex_t * pv; @@ -233,15 +230,15 @@ static void polyvertex_dump(polyvertex_t *pv) fixed_t y1 = pv->y * FRACUNIT; for (j = 0; j < numvertexes; j++) { - vertex_t * vt = &vertexes[j]; + vertex_t *vt = &vertexes[j]; if (abs(x1 - vt->x) + abs(y1 - vt->y) < 2 ) { - CONS_Debug(DBG_RENDER, " V%i(%6.2f, %6.2f)", j, pv->x, pv->y); + CONS_Debug(DBG_RENDER, "V%i(%6.2f, %6.2f)", j, pv->x, pv->y); return; } } - CONS_Debug(DBG_RENDER, " (%6.2f, %6.2f)", pv->x, pv->y); + CONS_Debug(DBG_RENDER, "(%6.2f, %6.2f)", pv->x, pv->y); } static void wpoly_dump(const char *str, wpoly_t *poly) @@ -260,7 +257,7 @@ static void wpoly_dump(const char *str, wpoly_t *poly) if (cnt > 120 ) { cnt = 6; - CONS_Debug(DBG_RENDER, "\n "); + CONS_Debug(DBG_RENDER, "\n"); } polyvertex_dump( poly->ppts[i]); cnt+=20; @@ -319,14 +316,14 @@ static void wpoly_move(wpoly_t * from_poly, /*OUT*/ wpoly_t * to_poly) // Does not alloc more, will limit copy to allocation size. static void wpoly_append(wpoly_t *src_poly, INT32 copy_from, INT32 copy_cnt, /*OUT*/ wpoly_t *dest_poly) { - polyvertex_t ** pvp; + polyvertex_t **pvp; INT32 n; #ifdef DEBUG_HWBSP - if (copy_cnt > src_poly->numpts ) + if (copy_cnt > src_poly->numpts) { - CONS_Debug(DBG_RENDER, "ERROR wpoly_append, exceeds src bounds, copy_from= %i, copy_cnt= %i, src numpts= %i\n", - copy_cnt, src_poly->numpts); + CONS_Debug(DBG_RENDER, "ERROR wpoly_append, exceeds src bounds, copy_from= %i, copy_cnt= %i, src numpts= %i\n", + copy_from, copy_cnt, src_poly->numpts); } #endif @@ -349,7 +346,7 @@ static void wpoly_append(wpoly_t *src_poly, INT32 copy_from, INT32 copy_cnt, /*O return; } - pvp = & dest_poly->ppts[dest_poly->numpts]; // append + pvp = &dest_poly->ppts[dest_poly->numpts]; // append dest_poly->numpts += copy_cnt; // before copy_cnt gets decremented n = src_poly->numpts - copy_from; // vertexes to end of poly @@ -364,9 +361,9 @@ static void wpoly_append(wpoly_t *src_poly, INT32 copy_from, INT32 copy_cnt, /*O } #ifdef DEBUG_HWBSP - if (copy_from + copy_cnt > src_poly->numpts ) // after wrap + if (copy_from + copy_cnt > src_poly->numpts) // after wrap { - CONS_Debug(DBG_RENDER, "ERROR wpoly_append, exceeds src bounds, copy_from= %i, copy_cnt= %i, src numpts= %i\n", + CONS_Debug(DBG_RENDER, "ERROR wpoly_append, exceeds src bounds, copy_from= %i, copy_cnt= %i, src numpts= %i\n", copy_from, copy_cnt, src_poly->numpts); } #endif @@ -384,7 +381,6 @@ static void wpoly_append(wpoly_t *src_poly, INT32 copy_from, INT32 copy_cnt, /*O } } - // Insert some new vertex, and then, // copy some of another poly to the destination poly. // v1, v2 : polyvertex to be inserted as first vertex of poly, in this order @@ -395,7 +391,7 @@ static void wpoly_split_copy(polyvertex_t * v1, polyvertex_t * v2, wpoly_t * src_poly, INT32 copy_from, INT32 copy_cnt, /*OUT*/ wpoly_t * dest_poly) { - polyvertex_t ** pvp; + polyvertex_t **pvp; INT32 n = 0; // Count the dest vertexes. @@ -463,7 +459,7 @@ static void wpoly_insert_vert(polyvertex_t *v1, INT32 v_at, /*INOUT*/ wpoly_t *x if (trigger_trace) { - CONS_Debug(DBG_RENDER, " Insert creates poly id=%i,%i,%i\n", xpoly->id1, xpoly->id2, xpoly->id3 ); + CONS_Debug(DBG_RENDER, "Insert creates poly id=%i,%i,%i\n", xpoly->id1, xpoly->id2, xpoly->id3); } #endif wpoly_init_alloc(numpts + 1, xpoly); @@ -512,25 +508,12 @@ static int total_subsecpoly_cnt = 0; // Polygon fast alloc / free // -------------------------------------------------------------------------- -#define ZPLANALLOC - -#ifndef ZPLANALLOC -#define POLY_ALLOCINC 4096 -#define POLY_VERTINC 256 -static byte* gr_polypool = NULL; -static unsigned int gr_polypool_free = 0; -#endif - static void HWR_Freepolysubsectors(void); // only between levels, clear poly pool static void HWR_ClearPolys(void) { - Z_FreeTags(PU_HWRPLANE, PU_HWRPLANE); -#ifndef ZPLANALLOC - gr_polypool = NULL; - gr_polypool_free = 0; -#endif + Z_FreeTag(PU_HWRPLANE); poly_vert = NULL; polyvert_store = NULL; } @@ -553,21 +536,7 @@ static poly_t* HWR_AllocPoly(INT32 numpts) size_t size; size = sizeof(poly_t) + sizeof(polyvertex_t) * numpts; -#ifdef ZPLANALLOC p = Z_Malloc(size, PU_HWRPLANE, NULL); -#else - if (gr_polypool_free < size) - { - // Allocate another pool. - // Z_FreeTags reclaims the leftover memory of previous pool. - gr_polypool_free = POLY_ALLOCINC; - gr_polypool = Z_Malloc(gr_polypool_free, PU_HWRPLANE, NULL); - } - - p = (poly_t*) gr_polypool; - gr_polypool += size; - gr_polypool_free -= size; -#endif p->numpts = numpts; return p; @@ -578,7 +547,7 @@ static poly_t* HWR_AllocPoly(INT32 numpts) void pwpoly(wpoly_t *poly) { int i; - for (i = 0; inumpts; i++) + for (i = 0; i < poly->numpts; i++) { if (poly->ppts[i]) printf("(%6.2f,%6.2f)", poly->ppts[i]->x, poly->ppts[i]->y); @@ -588,11 +557,11 @@ void pwpoly(wpoly_t *poly) } // print poly for debugging -void ppoly( poly_t * poly ) +void ppoly(poly_t *poly) { int i; - for ( i = 0; inumpts; i++ ) - printf( "(%6.2f,%6.2f)", poly->pts[i].x, poly->pts[i].y); + for (i = 0; i < poly->numpts; i++) + printf("(%6.2f,%6.2f)", poly->pts[i].x, poly->pts[i].y); printf("\n"); } #endif @@ -618,9 +587,9 @@ void ppoly( poly_t * poly ) typedef enum { DVL_none, // no divide - DVL_v1, // divide at v1 end of segment + DVL_v1, // divide at v1 end of segment DVL_mid, // divide between v1 and v2 - DVL_v2, // divide at v2 end of segment + DVL_v2, // divide at v2 end of segment } divline_e; typedef struct @@ -632,16 +601,16 @@ typedef struct #ifdef DEBUG_HWBSP static void fdivline_dump(const char * str, fdivline_t * dl) { - polyvertex_t v1; + polyvertex_t v1; v1.x = dl->x; v1.y = dl->y; CONS_Debug(DBG_RENDER, "%s", str); - polyvertex_dump( & v1); - CONS_Debug(DBG_RENDER, " to ", str); + polyvertex_dump(&v1); + CONS_Debug(DBG_RENDER, " to "); v1.x += dl->dx; v1.y += dl->dy; - polyvertex_dump( & v1); - CONS_Debug(DBG_RENDER, " slope (%f, %f)\n", dl->dx, dl->dy); + polyvertex_dump(&v1); + CONS_Debug(DBG_RENDER, "slope (%f, %f)\n", dl->dx, dl->dy); } #endif @@ -655,24 +624,27 @@ typedef struct } div_result_t; #ifdef DEBUG_HWBSP -static void divresult_dump( onst char * str, div_result_t * dr) +static void divresult_dump(const char *str, div_result_t *dr) { CONS_Debug(DBG_RENDER, "%s", str); CONS_Debug(DBG_RENDER, " CROSS %6.4f BEFORE v1+%i AFTER v1+%i ", dr->divfrac, dr->before, dr->after); - if (dr->at_vert ) + + if (dr->at_vert) { CONS_Debug(DBG_RENDER, " AT"); } - if (dr->vertex ) + + if (dr->vertex) { CONS_Debug(DBG_RENDER, " SEGPT"); - polyvertex_dump( dr->vertex); + polyvertex_dump(dr->vertex); } else { CONS_Debug(DBG_RENDER, " PT"); polyvertex_dump( &dr->divpt); } + CONS_Debug(DBG_RENDER, "\n"); } #endif @@ -765,7 +737,7 @@ static divline_e fracdivline(fdivline_t* partline, polyvertex_t* v1, polyvertex_ // Point is to rightside of divline when result > 0, // but result is multiplied by length of divline. // Returns near 0, when point is on, or nearly on, the divline. -static inline float point_rightside(fdivline_t * dl, polyvertex_t * v4) +static inline float point_rightside(fdivline_t *dl, polyvertex_t *v4) { // Cross product of dl and vector dl->(x,y) to v4, // is > 0 when v4 is to right side of divline. @@ -781,7 +753,7 @@ static inline float point_rightside(fdivline_t * dl, polyvertex_t * v4) // The cross product is > 0 when v4 is to the right side of the vector. // If the coordinates are rotated until the vector dy>0 and dx = 0, then the // cross product is > 0 when v4 is to the right of the vector. -static inline double cross_product(polyvertex_t * p1, polyvertex_t * p2, polyvertex_t * v4) +static inline double cross_product(polyvertex_t *p1, polyvertex_t *p2, polyvertex_t *v4) { return ( ((double)(v4->x) - (double)(p1->x)) * ((double)(p2->y) - (double)(p1->y)) @@ -804,7 +776,7 @@ typedef struct polytile_store_s wpoly_t *tile[POLYTILE_NUM_POLY]; } polytile_store_t; -// These are freed by Z_Free( PU_HWRPLANE ). +// These are freed by Z_FreeTag(PU_HWRPLANE). --lug: no theyre not lmao polytile_store_t *polytile_store = NULL; polytile_store_t *polytile_free = NULL; @@ -817,6 +789,9 @@ static void polytile_clean(void) polytile_store = ptp->next; Z_Free(ptp); } + + // null this just in case, we dont want any dingalinging pointers + polytile_free = NULL; } // Save the poly ptr within the polytile lists. @@ -848,7 +823,7 @@ static void polytile_enter(wpoly_t * poly) polytile_store->num_tile_used = 0; } - polytile_store->tile[ polytile_store->num_tile_used++ ] = poly; + polytile_store->tile[polytile_store->num_tile_used++] = poly; } static void polytile_remove(wpoly_t * poly) @@ -856,19 +831,19 @@ static void polytile_remove(wpoly_t * poly) polytile_store_t *ptp; wpoly_t **wpp; wpoly_t *lp; - INT32 i; + int i; // Search poly tiling. ptp = polytile_store; + while (ptp) { // Search for poly in all polytile_store_t - wpp = & ptp->tile[0]; + wpp = &ptp->tile[0]; for (i = ptp->num_tile_used-1; i >= 0; i--) { if (*wpp == poly) goto found; - wpp++; } ptp = ptp->next; @@ -882,7 +857,7 @@ found: *wpp = lp; // keep store compacted (ok if *wpp == lp already) // Remove last poly spot. - polytile_store->num_tile_used --; + polytile_store->num_tile_used--; if (polytile_store->num_tile_used == 0) { // Went empty, put on free list. @@ -990,8 +965,8 @@ static void SplitPoly(fdivline_t *dlnp, wpoly_t *poly, /*OUT*/ wpoly_t *frontpo INT32 A_before_wrap, B_after_wrap; #endif divline_e dle; - div_result_t A, B; // dividing points - div_result_t *result; + div_result_t A = {}, B = {}; // dividing points + div_result_t *result = NULL; #ifdef DEBUG_TRACE if (trigger_trace) @@ -1536,7 +1511,7 @@ static boolean apply_seg_chains(wpoly_t *poly) #endif #ifdef SEG_CHAIN_2 - if (! sctp->loose1 ) + if (! sctp->loose1) #endif { // Find original crossing point. @@ -1548,7 +1523,7 @@ static boolean apply_seg_chains(wpoly_t *poly) } #ifdef SEG_CHAIN_2 - if (! sctp->loose2 ) + if (! sctp->loose2) #endif { // Find original crossing point. @@ -1559,20 +1534,6 @@ static boolean apply_seg_chains(wpoly_t *poly) } } -#ifdef SEG_CHAIN_2 - if (sctp->loose1 && ! sctp->loose2 ) - { - } - - if (sctp->loose2 && ! sctp->loose1 ) - { - } - - if (sctp->loose1 && sctp->loose2 ) - { - } -#endif - if ((i1 < numpts) && (i2 < numpts)) { // Insert points are still there. @@ -1585,7 +1546,7 @@ static boolean apply_seg_chains(wpoly_t *poly) if (n < 0) n += numpts; - wpoly_init_alloc(sctp->num_seg + 1 + n, & comb_poly); + wpoly_init_alloc(sctp->num_seg + 1 + n, &comb_poly); #ifdef DEBUG_TRACE comb_poly.id3 = poly->id2; comb_poly.id2 = poly->id1; @@ -1607,7 +1568,7 @@ static boolean apply_seg_chains(wpoly_t *poly) if (n > 0) { // Save i2 to i1, which starts after rv2, to the comb_poly. - wpoly_append(poly, i2, n, /*OUT*/ & comb_poly); + wpoly_append(poly, i2, n, /*OUT*/ &comb_poly); } wpoly_move(&comb_poly, /*OUT*/ poly); // empty comb_poly @@ -1650,8 +1611,8 @@ static void CutOutSubsecPoly(INT32 ssindex, /*INOUT*/ wpoly_t* poly) fdivline_t cutseg; // x,y,dx,dy as start of node_t struct divline_e dle; - div_result_t A, B; // dividing points - div_result_t *result; + div_result_t A = {}, B = {}; // dividing points + div_result_t *result = NULL; INT32 poly_num_pts, ps, n; INT32 i1, i2; @@ -1736,7 +1697,8 @@ static void CutOutSubsecPoly(INT32 ssindex, /*INOUT*/ wpoly_t* poly) // the same vertex. It is NULL for DVL_mid. // When dividing point is at a vertex, is found at next segment too. if (B.vertex // ( dle == DVL_v1 || dle == DVL_v2 ) - && B.vertex == A.vertex ) continue; + && B.vertex == A.vertex) + continue; // Split at B // Dependent upon dle, setup in fracdivline. @@ -2084,7 +2046,6 @@ static void HWR_WalkBSPNode(INT32 bspnum, wpoly_t* poly, UINT16 *leafnode, fixed trigger_trace = 1; } #endif - HWR_SubsecPoly(subsecnum, poly); // Add the poly points into the bounding box. @@ -2094,7 +2055,6 @@ static void HWR_WalkBSPNode(INT32 bspnum, wpoly_t* poly, UINT16 *leafnode, fixed pt = poly->ppts[i]; M_AddToBox(bbox, (fixed_t)(pt->x * FRACUNIT), (fixed_t)(pt->y * FRACUNIT)); } - #ifdef POLYTILE polytile_remove(poly); #endif @@ -2103,12 +2063,10 @@ static void HWR_WalkBSPNode(INT32 bspnum, wpoly_t* poly, UINT16 *leafnode, fixed #ifdef POLYTILE polytile_enter(&wpoly_subsectors[subsecnum]); #endif - #ifdef DEBUG_TRACE if (trigger_trace == 1) // only turn off specifc subsector traces trigger_trace = 0; #endif - #ifdef HWR_LOADING_SCREEN //Hurdler: implement a loading status if (ls_count-- <= 0) @@ -2152,7 +2110,6 @@ static void HWR_WalkBSPNode(INT32 bspnum, wpoly_t* poly, UINT16 *leafnode, fixed CONS_Debug(DBG_RENDER, "BSP-FRONT %i:\n", frontpoly.id1); } #endif - HWR_WalkBSPNode(bsp->children[0], &frontpoly, &bsp->children[0], bsp->bbox[0]); // copy child bbox @@ -2166,7 +2123,6 @@ static void HWR_WalkBSPNode(INT32 bspnum, wpoly_t* poly, UINT16 *leafnode, fixed CONS_Debug(DBG_RENDER, "BSP-FRONT %i EMPTY:\n", backpoly.id1); } #endif - // [WDJ] Having no front poly is as likely as no back poly, since // logic in Split Poly was changed to check poly direction. // I_Error ("HWR_WalkBSPNode: no front poly, bspnum= %d\n", bspnum); // I_SoftError @@ -2319,12 +2275,12 @@ static int num_T_vertex_fixed; // A structure to pass in BSP recursion, reducing it to one parameter. typedef struct { - fixed_t max_x, min_x, max_y, min_y; - wpoly_t * poly; // our poly - polyvertex_t * pt; // T-split vertex - polyvertex_t * before, * after; // shared vertex before and after it - int our_secnum, find_secnum; // sectors - int pt_index; + fixed_t max_x, min_x, max_y, min_y; + wpoly_t *poly; // our poly + polyvertex_t *pt; // T-split vertex + polyvertex_t *before, *after; // shared vertex before and after it + //int our_secnum, find_secnum; // sectors + int pt_index; } split_T_t; // Dist 0.4999 cures HOM in Freedoom map09 @@ -2341,6 +2297,7 @@ static void SearchSegInBSP(INT32 bspnum, split_T_t * stp) const fixed_t maxy = stp->max_y; const fixed_t maxx = stp->max_x; const fixed_t miny = stp->min_y; + const fixed_t minx = stp->min_x; for (;;) { @@ -2348,27 +2305,22 @@ static void SearchSegInBSP(INT32 bspnum, split_T_t * stp) goto got_subsector; const node_t *node = &nodes[bspnum]; - const fixed_t *bbox0 = nodes[bspnum].bbox[0]; - - const boolean left = - ((bbox0[BOXBOTTOM] <= maxy) & - (bbox0[BOXTOP] >= miny) & - (bbox0[BOXLEFT] <= maxx) & - (bbox0[BOXRIGHT] >= miny)); + const fixed_t *bbox0 = node->bbox[0]; + const fixed_t *bbox1 = node->bbox[1]; // Not a subsector, visit left and right children. - if (left) + if ( (bbox0[BOXBOTTOM] <= maxy) + && (bbox0[BOXTOP ] >= miny) + && (bbox0[BOXLEFT ] <= maxx) + && (bbox0[BOXRIGHT ] >= minx) + ) SearchSegInBSP(node->children[0], stp); - const fixed_t *bbox1 = nodes[bspnum].bbox[1]; - - const boolean right = - ((bbox1[BOXBOTTOM] <= maxy) & - (bbox1[BOXTOP] >= miny) & - (bbox1[BOXLEFT] <= maxx) & - (bbox1[BOXRIGHT] >= miny)); - - if (!right) + if (! ((bbox1[BOXBOTTOM] <= maxy) + && (bbox1[BOXTOP ] >= miny) + && (bbox1[BOXLEFT ] <= maxx) + && (bbox1[BOXRIGHT ] >= minx) + ) ) break; // Tail recursion within loop. @@ -2383,7 +2335,7 @@ got_subsector: return; // For every subsector polygon different than poly - wq = & wpoly_subsectors[subsecnum]; + wq = &wpoly_subsectors[subsecnum]; if (wq == stp->poly || !wq) return; @@ -2540,7 +2492,7 @@ static sector_t *find_poly_sector(wpoly_t *ssp) // But a linedef may be much closer along the other axis, so we test along both X and Y. for (k = 0; k < numlines; k++) { - line_t *lp = & lines[k]; + line_t *lp = &lines[k]; if (lp->frontsector == lp->backsector) continue; // self-ref lines lie. @@ -2658,7 +2610,7 @@ static void AdjustSegs(void) // for all segs in all sectors for (ssnum = 0; ssnum < numsubsectors; ssnum++) { - wp = & wpoly_subsectors[ssnum]; + wp = &wpoly_subsectors[ssnum]; if (wp->numpts == 0) continue; @@ -2853,7 +2805,7 @@ static void finalize_polygons(void) // For all segs in all sectors. for (ssnum = 0; ssnum < numsubsectors; ssnum++) { - wpoly = & wpoly_subsectors[ssnum]; + wpoly = &wpoly_subsectors[ssnum]; #ifdef DEBUG_TRACE if (trigger_subsector == 0xFFFFFFF2 || trigger_subsector == ssnum) @@ -2866,7 +2818,7 @@ static void finalize_polygons(void) poly_subsectors[ssnum].planepoly = dpoly; pv = dpoly->pts; - for ( ps = 0; ps < wpoly->numpts; ps++ ) + for (ps = 0; ps < wpoly->numpts; ps++) { *pv++ = *(wpoly->ppts[ps]); // copy of each vertex } From 9a3bb0023d6500cb4b5656d0587d004544644ef4 Mon Sep 17 00:00:00 2001 From: NepDisk Date: Mon, 9 Mar 2026 22:16:18 -0400 Subject: [PATCH 18/18] wadnamelinux: check both tmp and temp --- src/w_wad.c | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/w_wad.c b/src/w_wad.c index b2ca0d1d5..aab5e5f58 100644 --- a/src/w_wad.c +++ b/src/w_wad.c @@ -625,21 +625,13 @@ static lumpinfo_t* ResGetLumpsWad (FILE* handle, UINT16* nlmp, const char* filen { const char *temp = trimname-1; // CONS_Printf("wadnumtemp: %s\n", temp); -#if (defined(_WIN32)) while (temp >= filename+5 && *temp != PATHSEP[0]) temp--; if (((trimname-1) - temp) == 8 && temp >= filename+5 - && !strncmp(temp-5, PATHSEP"Temp", 5)) -#elif (defined(__linux__)) - while (temp >= filename+4 && *temp != PATHSEP[0]) - temp--; - - if (((trimname-1) - temp) == 8 - && temp >= filename+4 - && !strncmp(temp-4, PATHSEP"tmp", 4)) -#endif + && (!strncmp(temp-5, PATHSEP"Temp", 5) + || !strncmp(temp-4, PATHSEP"tmp", 4))) { filename = wadfiles[ ((wadnamelump & ~UINT16_MAX) >> 16)