From 2325b7bee3cd7adaef8bf2996ddac5d815a1fb5a Mon Sep 17 00:00:00 2001 From: Dasho Danger Date: Fri, 19 Mar 2021 09:27:43 +0430 Subject: [PATCH 01/20] Re-working Hexen prefab support --- games/hexen/themes.lua | 5 + gui/dm_prefab.cc | 44 + gui/m_lua.cc | 2 + scripts/prefab.lua | 144 ++- scripts/prefab_hexen.lua | 2503 -------------------------------------- 5 files changed, 192 insertions(+), 2506 deletions(-) delete mode 100644 scripts/prefab_hexen.lua diff --git a/games/hexen/themes.lua b/games/hexen/themes.lua index 73abd55f5d..86d119bbff 100644 --- a/games/hexen/themes.lua +++ b/games/hexen/themes.lua @@ -86,6 +86,11 @@ HEXEN.THEMES = beam_metal = 50, }, + wall_groups = + { + PLAIN = 0.01, + }, + fences = { CASTLE07=35, diff --git a/gui/dm_prefab.cc b/gui/dm_prefab.cc index 1c5b1e5da8..aa4b8a70d0 100644 --- a/gui/dm_prefab.cc +++ b/gui/dm_prefab.cc @@ -318,6 +318,50 @@ int wadfab_get_line(lua_State *L) lua_pushinteger(L, LD->tag); lua_setfield(L, -2, "tag"); + + return 1; +} + +int wadfab_get_line_hexen(lua_State *L) +{ + int index = luaL_checkinteger(L, 1); + + if (index < 0 || index >= ajpoly::num_linedefs) + return 0; + + const ajpoly::linedef_c * LD = ajpoly::Linedef(index); + + lua_newtable(L); + + lua_pushinteger(L, LD->start->x); + lua_setfield(L, -2, "x1"); + + lua_pushinteger(L, LD->start->y); + lua_setfield(L, -2, "y1"); + + lua_pushinteger(L, LD->end->x); + lua_setfield(L, -2, "x2"); + + lua_pushinteger(L, LD->end->y); + lua_setfield(L, -2, "y2"); + + if (LD->right) + { + lua_pushinteger(L, LD->right->index); + lua_setfield(L, -2, "right"); + } + + if (LD->left) + { + lua_pushinteger(L, LD->left->index); + lua_setfield(L, -2, "left"); + } + + lua_pushinteger(L, LD->special); + lua_setfield(L, -2, "special"); + + lua_pushinteger(L, LD->flags); + lua_setfield(L, -2, "flags"); lua_pushinteger(L, LD->args[0]); lua_setfield(L, -2, "arg1"); diff --git a/gui/m_lua.cc b/gui/m_lua.cc index 006dd6521f..fe19b647b4 100644 --- a/gui/m_lua.cc +++ b/gui/m_lua.cc @@ -870,6 +870,7 @@ extern int wadfab_get_polygon(lua_State *L); extern int wadfab_get_sector(lua_State *L); extern int wadfab_get_side(lua_State *L); extern int wadfab_get_line(lua_State *L); +extern int wadfab_get_line_hexen(lua_State *L); extern int wadfab_get_3d_floor(lua_State *L); extern int wadfab_get_thing(lua_State *L); @@ -969,6 +970,7 @@ static const luaL_Reg gui_script_funcs[] = { "wadfab_get_sector", wadfab_get_sector }, { "wadfab_get_side", wadfab_get_side }, { "wadfab_get_line", wadfab_get_line }, + { "wadfab_get_line_hexen", wadfab_get_line_hexen }, { "wadfab_get_3d_floor", wadfab_get_3d_floor }, { "wadfab_get_thing", wadfab_get_thing }, diff --git a/scripts/prefab.lua b/scripts/prefab.lua index aed5c025ae..4190693983 100644 --- a/scripts/prefab.lua +++ b/scripts/prefab.lua @@ -1200,6 +1200,132 @@ function Fab_load_wad(def) return C2 end + + local function decode_polygon_side_hexen(sec, C, pass) + -- pass is 1 for floor, 2 for ceiling + -- sec will be NIL for a polygon in void space + + local C2 = { x=C.x, y=C.y } + + C2.u1_along = C.along + + local side + local line + + if C.side then side = gui.wadfab_get_side(C.side) end + if C.line then line = gui.wadfab_get_line_hexen(C.line) end + + -- get other sector (which the polygon side faces) + local other_sec + + if line and side and side.sector then + other_sec = gui.wadfab_get_sector(side.sector) + end + + local flags = (line and line.flags) or 0 + + local two_sided = (line and line.left and line.right) + + + --- determine texture to use --- + + local upper_tex + local lower_tex + local mid_tex + + upper_tex = side and side.upper_tex + if upper_tex == "-" then upper_tex = nil end + + lower_tex = side and side.lower_tex + if lower_tex == "-" then lower_tex = nil end + + mid_tex = side and side.mid_tex + if mid_tex == "-" then mid_tex = nil end + + + local tex + + -- if line is one-sided, use the middle texture + if line and not two_sided then + tex = mid_tex + + elseif pass == 1 then + tex = lower_tex or upper_tex + + else + tex = upper_tex or lower_tex + end + + if tex then + C2.tex = tex + end + + + -- line type -- + + if line and line.special and line.special > 0 then + C2.special = line.special + end + + if line and line.arg1 and line.arg1 >= 0 then + C2.arg1 = line.arg1 + end + + if line and line.arg2 and line.arg2 >= 0 then + C2.arg2 = line.arg2 + end + + if line and line.arg3 and line.arg3 >= 0 then + C2.arg3 = line.arg3 + end + + if line and line.arg4 and line.arg4 >= 0 then + C2.arg4 = line.arg4 + end + + if line and line.arg5 and line.arg5 >= 0 then + C2.arg5 = line.arg5 + end + + -- line flags -- + + local MLF_UpperUnpegged = 0x0008 + local MLF_LowerUnpegged = 0x0010 + + local upper_unpeg + local lower_unpeg + + if not line then + -- nothing + + else + -- keep these flags: block-all, block-mon, secret, no-draw, + -- always-draw, block-sound, pass-thru + flags = bit.band(flags, 0xFFF) + + if flags ~= 0 then + C2.flags = flags + + -- this makes sure the flags get applied + if not C2.special then C2.special = 0 end + end + + upper_unpeg = (bit.band(flags, MLF_UpperUnpegged) ~= 0) + lower_unpeg = (bit.band(flags, MLF_LowerUnpegged) ~= 0) + end + + -- offsets -- + + if heights_are_same(sec, other_sec, pass) then + -- do not copy the offsets to the brush + + elseif side and line then + C2.u1 = convert_offset(side.x_offset) + C2.v1 = convert_offset(side.y_offset) + end + + return C2 + end local function decode_3d_floor_side(exfl, C) local C2 = { x=C.x, y=C.y } @@ -1221,7 +1347,11 @@ function Fab_load_wad(def) for _,C in pairs(coords) do - table.insert(B, decode_polygon_side(nil, C, 1)) + if OB_CONFIG.game == "hexen" then + table.insert(B, decode_polygon_side_hexen(nil, C, 1)) + else + table.insert(B, decode_polygon_side(nil, C, 1)) + end end -- add this new brush to the prefab @@ -1262,7 +1392,11 @@ function Fab_load_wad(def) decode_lighting(S, B[1]) for _,C in pairs(coords) do - table.insert(B, decode_polygon_side(S, C, 1)) + if OB_CONFIG.game == "hexen" then + table.insert(B, decode_polygon_side_hexen(S, C, 1)) + else + table.insert(B, decode_polygon_side(S, C, 1)) + end end table.insert(fab.brushes, B) @@ -1354,7 +1488,11 @@ function Fab_load_wad(def) end for _,C in pairs(coords) do - table.insert(B, decode_polygon_side(S, C, pass)) + if OB_CONFIG.game == "hexen" then + table.insert(B, decode_polygon_side_hexen(S, C, pass)) + else + table.insert(B, decode_polygon_side(S, C, pass)) + end end -- add this new brush to the prefab diff --git a/scripts/prefab_hexen.lua b/scripts/prefab_hexen.lua deleted file mode 100644 index 2e030f765f..0000000000 --- a/scripts/prefab_hexen.lua +++ /dev/null @@ -1,2503 +0,0 @@ ------------------------------------------------------------------------- --- WAD PREFAB SYSTEM ------------------------------------------------------------------------- --- --- // Obsidian // --- --- Copyright (C) 2013-2017 Andrew Apted --- Copyright (C) 2019 MsrSgtShooterPerson --- --- This program is free software; you can redistribute it and/or --- modify it under the terms of the GNU General Public License --- as published by the Free Software Foundation; either version 2, --- of the License, or (at your option) any later version. --- --- This program is distributed in the hope that it will be useful, --- but WITHOUT ANY WARRANTY; without even the implied warranty of --- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --- GNU General Public License for more details. --- ------------------------------------------------------------------------- - - -WADFAB_ENTITIES = -{ - -- monster spots - - [8102] = { kind="monster", r= 20 }, - [8103] = { kind="monster", r= 32 }, - [8104] = { kind="monster", r= 48 }, - [8106] = { kind="monster", r= 64 }, - [8108] = { kind="monster", r=128 }, - - [8112] = { kind="flyer", r= 20 }, - [8113] = { kind="flyer", r= 32 }, - [8114] = { kind="flyer", r= 48 }, - [8116] = { kind="flyer", r= 64 }, - [8118] = { kind="flyer", r=128 }, - - [8122] = { kind="cage", r= 20 }, - [8123] = { kind="cage", r= 32 }, - [8124] = { kind="cage", r= 48 }, - [8126] = { kind="cage", r= 64 }, - [8128] = { kind="cage", r=128 }, - - [8132] = { kind="trap", r= 20 }, - [8133] = { kind="trap", r= 32 }, - [8134] = { kind="trap", r= 48 }, - [8136] = { kind="trap", r= 64 }, - [8138] = { kind="trap", r=128 }, - - -- special spots - - [8151] = { kind="pickup", r=16 }, - [8152] = { kind="big_item", r=16 }, - [8160] = { kind="important", r=64 }, - - -- lighting - - [8181] = { kind="light" }, - - -- souuuuund - - [8185] = { kind="sound" }, - - -- miscellaneous - - [8199] = { kind="secret" }, -}, - - -WADFAB_FX_DELTAS = -{ - [1] = 48 -- random off - [2] = 48 -- blink fast - [12] = 48 -- blink fast, sync - [3] = 48 -- blink slow - [13] = 48 -- blink slow, sync - [17] = 48 -- flickers - - [8] = 128 -- oscillates -}, - - -WADFAB_REACHABLE = 992, -WADFAB_MOVER = 995, -WADFAB_DOOR = 996, -WADFAB_DELTA_12 = 997, -WADFAB_LIGHT_BRUSH = 987, - - - -function Fab_load_all_definitions() - - local function load_from_subdir(top_level, sub) - -- ignore the attic (it contains a lot of broken stuff) - if sub == "_attic" then return end - - local dir = top_level .. "/" .. sub - - local list, err = gui.scan_directory(dir, "*.lua") - - if list == nil then - gui.printf("Failed to scan prefab directory '%s'\n", sub) - return - end - - gui.set_import_dir(dir) - - for _,filename in pairs(list) do - gui.debugf("Loading %s/%s\n", sub, filename) - - gui.import(filename) - end - - gui.set_import_dir("") - end - - - local function visit_dir(top_level) - gui.printf("Loading prefabs from: '%s'\n", top_level) - - local subdirs, err = gui.scan_directory(top_level, "DIRS") - - if not subdirs then - gui.printf("Failed to scan folder: %s\n", tostring(err)) - return - end - - for _,sub in pairs(subdirs) do - load_from_subdir(top_level, sub) - end - - -- give each loaded definition a 'dir_name' field. - -- [ we assume previous defs also got it, hence this will only set - -- the dir_name in the definitions just loaded ] - - for name,def in pairs(PREFABS) do - if not def.dir_name then - def.dir_name = top_level - end - end - - gui.printf("OK\n") - end - - - local function kind_from_filename(filename) - assert(filename) - - local kind = string.match(filename, "([%w_]+)/") - - if not kind then - error("weird prefab filename: " .. tostring(filename)) - end - - return kind - end - - - local function random_factor(def) - if not def.prob_skew then return 1 end - - local prob_skew = def.prob_skew - local half_skew = (1.0 + prob_skew) / 2.0, - - return rand.pick({ 1 / prob_skew, 1 / half_skew, 1.0, half_skew, prob_skew }) - end - - - local function calc_prob(def) - - -- attachment for the Hideous Destructor cover walls - if PARAM["hd_cover_walls"] ~= "enable" then - if def.is_hideous_destructor_fab == true then - def.skip_prob = 100, - end - end - - -- attachment for fabs that use Armaetus's Epic textures - if def.texture_pack then - if def.texture_pack == "armaetus", - and not PARAM["epic_textures_activated"] then - def.skip_prob = 100, - end - end - - -- support for the new replace field - if PARAM.epic_textures_activated then - if def.replaces then - - -- HARD replace mode causes pre-existing fabs to be removed - -- entirely, to be replaced with the replacing fab. - if def.replace_mode == "hard" then - PREFABS[def.replaces] = def - table.remove(PREFABS[def.name]) - - -- remove templates of a replaced fab as well - for name,odef in pairs(PREFABS) do - if odef.template == def.replaces then - table.remove(PREFABS[odef]) - end - end - end - - -- SOFT replace mode simply causes pre-existing fabs to - -- have a probability of 0. This is more prefered as it - -- is more likely to not break things. - if not def.replace_mode or def.replace_mode == "soft" then - PREFABS[def.replaces].prob = 0, - PREFABS[def.replaces].use_prob = 0, - PREFABS[def.replaces].skip_prob = 100, - - for name,odef in pairs(PREFABS) do - if odef.template == def.replaces then - PREFABS[odef].prob = 0, - PREFABS[odef].use_prob = 0, - PREFABS[odef].skip_prob = 100, - end - end - end - end - end - - - - if def.skip_prob then - if rand.odds(def.skip_prob) then return 0 end - end - - -- check against current game, engine, theme (etc...) - if not ob_match_game(def) then return 0 end - if not ob_match_engine(def) then return 0 end - if not ob_match_playmode(def) then return 0 end - - -- normal logic -- - - local prob = def.prob or 0, - - prob = prob * random_factor(def) - - return prob - end - - - local function preprocess_all() - table.name_up(PREFABS) - table.expand_templates(PREFABS) - - local count = 0, - - for name,def in pairs(PREFABS) do - if not def.kind then - def.kind = kind_from_filename(def.file) - end - - def.use_prob = calc_prob(def) - - count = count + 1, - end - - gui.printf(count .. " prefab definitions loaded!\n\n") - end - - - ---| Fab_load_all_definitions |--- - - PREFABS = {}, - - assert(GAME.game_dir) - - visit_dir("games/" .. GAME.game_dir .. "/fabs") - - preprocess_all() -end - - - -function Fab_update_skip_prob() - for name,def in pairs(PREFABS) do - if def.skip_prob then - if rand.odds(def.skip_prob) then - def.use_prob = 0, - else - def.use_prob = def.prob - end - end - end -end - - - -function Fab_expansion_groups(list, axis_name, fit_size, pf_size, fabinfo) - local extra = fit_size - pf_size - - -- nothing needed if the size is the same - if math.abs(extra) < 1 then return nil end - - - if extra < 0 then - local problem_string = "\n\nPREFAB DOES NOT FIT!!!\n", - problem_string = problem_string .. "(on " .. axis_name .. " axis)\n", - problem_string = problem_string .. "Fab info:\n", - problem_string = problem_string .. table.tostr(fabinfo) .. "\n", - problem_string = problem_string .. "Required: " .. fit_size .. " Prefab has: " .. pf_size .. "\n\n", - gui.printf(problem_string) - end - - --assert(extra > 0) - - -- check some special keywords. - -- missing 'x_fit' field (etc) defaults to "stretch", - - if not list or list == "stretch" then - local G = - { - low = 0, - high = pf_size - low2 = 0, - high2 = fit_size - }, - - G.size = G.high - G.low - G.size2 = G.high2 - G.low2, - - return { G }, - - elseif list == "left" or list == "bottom" then - list = { 0, 1 }, - - elseif list == "right" or list == "top" then - list = { pf_size - 1, pf_size }, - - elseif list == "frame" then - list = { 0, 1, pf_size - 1, pf_size }, - end - - - if type(list) ~= "table" then - error("Bad " .. axis_name .. "_fit field in prefab: " .. tostring(list)) - end - - - -- validate list - for i = 1, #list-1 do - local A = list[i] - local B = list[i + 1] - - if A >= B then - error("Bad ordering in " .. axis_name .. "_fit field in prefab") - end - end - - - -- compute total weight of expanding sections - local total_weight = 0, - - for i = 1, #list-1, 2 do - local weight = list[i+1] - list[i] - total_weight = total_weight + weight - end - - assert(total_weight > 0) - - - -- construct the mapping groups - local groups = { }, - local pos = list[1] - - for i = 1,#list-1 do - local G = - { - low = list[i] - high = list[i+1] - }, - - G.size = G.high - G.low - - G.size2 = G.size - - if (i % 2) == 1 then - local weight = list[i+1] - list[i] - G.size2 = G.size2 + extra * weight / total_weight - end - - G.low2 = pos - G.high2 = pos + G.size2, - - pos = pos + G.size2, - - table.insert(groups, G) - end - - return groups -end - - - -function is_subst(value) - return type(value) == "string" and string.match(value, "^[!?]") -end - - -function Fab_apply_substitute(value, SKIN) - assert(is_subst(value)) - - -- a simple substitution is just: "?varname", - -- a more complex one has an operator: "?varname+3", "?foo==1", - - local neg, var_name, op, number = string.match(value, "(.)([%w_]*)(%p*)(%-?[%d.]*)"); - - if var_name == "" then var_name = nil end - if op == "" then op = nil end - if number == "" then number = nil end - - if not var_name or (op and not number) or (op and neg == '!') then - error("bad substitution: " .. tostring(value)); - end - - -- first lookup variable name, abort if not present - value = SKIN[var_name] - - if value == nil then - return nil - end - - -- recursive substitution is handled by caller - if is_subst(value) then - if op then - error("subst op failed on recursive var: " .. var_name) - end - - return value - end - - -- apply the boolean negation - if neg == '!' then - return 1 - convert_bool(value) - - -- apply the operator - elseif op then - value = 0 + value - number = 0 + number - - if op == "+" then return value + number end - if op == "-" then return value - number end - - if op == "==" then return sel(value == number, 1, 0) end - if op == "~=" then return sel(value != number, 1, 0) end - - error("bad subst operator: " .. tostring(op)) - end - - return value -end - - - -function Fab_determine_bbox(fab) - local x1, y1, z1, - local x2, y2, z2, - - -- Note: no need to handle slopes, they are defined to be "shrinky", - -- (i.e. never higher that t, never lower than b). - - for _,B in pairs(pairs(fab.brushes)) do - if B[1].outlier then continue end - if B[1].m == "light" then continue end - if B[1].m == "rail" then continue end - if B[1].m == "spot" then continue end - - for _,C in pairs(pairs(B)) do - - if C.x then - if not x1 then - x1, y1 = C.x, C.y - x2, y2 = C.x, C.y - else - x1 = math.min(x1, C.x) - y1 = math.min(y1, C.y) - x2 = math.max(x2, C.x) - y2 = math.max(y2, C.y) - end - - elseif C.b or C.t then - local z = C.b or C.t - if not z1 then - z1, z2 = z, z - else - z1 = math.min(z1, z) - z2 = math.max(z2, z) - end - end - - end -- C - end -- B - - assert(x1 and y1 and x2 and y2) - - -- Note: it is OK when z1 and z2 are not set (this happens with - -- prefabs consisting entirely of infinitely tall solids). - - -- Note: It is possible to get dz == 0, - - local dz - if z1 then dz = z2 - z1 end - - fab.bbox = { x1=x1, x2=x2, dx=(x2 - x1), - y1=y1, y2=y2, dy=(y2 - y1), - z1=z1, z2=z2, dz=dz, - }, - - gui.debugf("bbox =\n%s\n", table.tostr(fab.bbox)) -end - - - -function Fab_transform_XY(fab, T) - - local function brush_xy(brush) - for _,C in pairs(pairs(brush)) do - if C.x then C.x, C.y = Trans.apply_xy(C.x, C.y) end - - if C.slope then C.slope = Trans.apply_slope(C.slope) end - if C.angle then C.angle = Trans.apply_angle(C.angle) end - end - - if sel(T.mirror_x, 1, 0) ~= sel(T.mirror_y, 1, 0) then - brushlib.reverse(brush) - end - end - - - local function entity_xy(E) - if E.x then - E.x, E.y = Trans.apply_xy(E.x, E.y) - end - - if E.angle then - E.angle = Trans.apply_angle(E.angle) - end - - if E.angles then - E.angles = Trans.apply_angles_xy(E.angles) - end - end - - - local function model_xy(M) - M.x1, M.y1 = Trans.apply_xy(M.x1, M.y1) - M.x2, M.y2 = Trans.apply_xy(M.x2, M.y2) - - -- handle rotation / mirroring - -- NOTE: we only support 0/90/180/270 rotations - - if M.x1 > M.x2 then M.x1, M.x2 = M.x2, M.x1 ; M.y_face.u1, M.y_face.u2 = M.y_face.u2, M.y_face.u1 end - if M.y1 > M.y2 then M.y1, M.y2 = M.y2, M.y1 ; M.x_face.u1, M.x_face.u2 = M.x_face.u2, M.x_face.u1 end - - -- handle 90 and 270 degree rotations : swap X and Y faces - local rotate = T.rotate or 0, - - if math.abs(T.rotate - 90) < 15 or math.abs(T.rotate - 270) < 15 then - M.x_face, M.y_face = M.y_face, M.x_face - end - end - - - ---| Fab_transform_XY |--- - - assert(fab.state == "skinned") - - fab.state = "transform_xy", - - Trans.set(T) - - local bbox = fab.bbox - - --- X --- - - if fab.x_fit or T.fitted_x then - if not T.fitted_x then - error("Fitted prefab used without fitted X transform Culprit: " .. fab.map .. " from " .. fab.name) - - elseif T.scale_x then - error("Fitted transform used with scale_x Culprit: " .. fab.map .. " from " .. fab.name) - - elseif math.abs(bbox.x1) > 0.1 then - error("Fitted prefab must have lowest X coord at 0. Culprit: " .. fab.map .. " from " .. fab.name) - end - - Trans.TRANSFORM.groups_x = Fab_expansion_groups(fab.x_fit, "x", T.fitted_x, bbox.x2, fab) - - - else - -- "loose" placement - end - - - --- Y --- - - if fab.y_fit or T.fitted_y then - if not T.fitted_y then - error("Fitted prefab used without fitted Y transform. Culprit: " .. fab.map .. " from " .. fab.name) - - elseif T.scale_y then - error("Fitted transform used with scale_y. Culprit: " .. fab.map .. " from " .. fab.name) - - elseif math.abs(bbox.y1) > 0.1 then - error("Fitted prefab must have lowest Y coord at 0. Culprit: " .. fab.map .. " from " .. fab.name) - end - - Trans.TRANSFORM.groups_y = Fab_expansion_groups(fab.y_fit, "y", T.fitted_y, bbox.y2, fab) - - else - -- "loose" placement - end - - -- apply the coordinate transform to all parts of the prefab - - for _,B in pairs(pairs(fab.brushes)) do - brush_xy(B) - end - - for _,E in pairs(pairs(fab.entities)) do - entity_xy(E) - end - - for _,M in pairs(pairs(fab.models)) do - model_xy(M) - entity_xy(M.entity) - end - - Trans.clear() -end - - - -function Fab_transform_Z(fab, T) - - local function brush_z(brush) - local b, t - - for _,C in pairs(pairs(brush)) do - if C.b then C.b = Trans.apply_z(C.b) ; b = C.b end - if C.t then C.t = Trans.apply_z(C.t) ; t = C.t end - end - - -- apply capping - if Trans.z1_cap and not b and (not t or t.t > Trans.z1_cap) then - table.insert(brush, { b = Trans.z1_cap }) - end - - if Trans.z2_cap and not t and (not b or b.b < Trans.z2_cap) then - table.insert(brush, { t = Trans.z2_cap }) - end - end - - - local function entity_z(E) - if E.z then - E.z = Trans.apply_z(E.z) - - if E.delta_z then - E.z = E.z + E.delta_z - E.delta_z = nil - end - - if E.angles then - E.angles = Trans.apply_angles_z(E.angles) - end - end - end - - - local function model_z(M) - M.z1 = Trans.apply_z(M.z1) - M.z2 = Trans.apply_z(M.z2) - - if M.delta_z then - M.z1 = M.z1 + M.delta_z - M.z2 = M.z2 + M.delta_z - end - - if Trans.mirror_z then - M.z1, M.z2 = M.z2, M.z1, - end - - -- handle QUAKE I / II platforms - if M.entity.height and T.scale_z then - M.entity.height = M.entity.height * T.scale_z - end - end - - - ---| Fab_transform_Z |--- - - assert(fab.state == "transform_xy") - - fab.state = "transform_z", - - Trans.set(T) - - local bbox = fab.bbox - - --- Z --- - - if fab.z_fit or T.fitted_z then - if not T.fitted_z then - error("Fitted prefab used without fitted Z transform. Culprit: " .. fab.map .. " from " .. fab.name) - - elseif T.scale_z then - error("Fitted transform used with scale_z. Culprit: " .. fab.map .. " from " .. fab.name) - - elseif not (bbox.dz and bbox.dz >= 1) then - error("Fitted prefab has no vertical range! Culprit: " .. fab.map .. " from " .. fab.name) - - elseif math.abs(bbox.z1) > 0.1 then - error("Fitted prefab must have lowest Z coord at 0. Culprit: " .. fab.map .. " from " .. fab.name .. - " Bounding box z1: " .. bbox.z1) - end - - Trans.TRANSFORM.groups_z = Fab_expansion_groups(fab.z_fit, "z", T.fitted_z, bbox.z2, fab) - - else - -- "loose" mode - end - - -- apply the coordinate transform to all parts of the prefab - - for _,B in pairs(pairs(fab.brushes)) do - brush_z(B) - end - - for _,E in pairs(pairs(fab.entities)) do - entity_z(E) - end - - for _,M in pairs(pairs(fab.models)) do - model_z(M) - end - - Trans.clear() -end - - - -function Fab_bound_brushes_Z(fab, z1, z2) - if not (z1 or z2) then return end - - for _,B in pairs(pairs(fab.brushes)) do - local b = Brush_get_b(B) - local t = Brush_get_t(B) - - if z1 and not b then table.insert(B, { b = z1 }) end - if z2 and not t then table.insert(B, { t = z2 }) end - end -end - - - -function Fab_render(fab) - - assert(fab.state == "transform_z") - - fab.state = "rendered", - local fab_map - - if fab.map then - fab_map = fab.map - else - fab_map = "object", - end - - for _,B in pairs(pairs(fab.brushes)) do - if B[1].m ~= "spot" then - raw_add_brush(B) - end - end - - for _,M in pairs(pairs(fab.models)) do - raw_add_model(M) - end - - for _,E in pairs(pairs(fab.entities)) do - if E.id then - raw_add_entity(E) - end - end -end - - - -function Fab_solid_entities(fab, room) - -- prefab must be rendered (or ready to render) - - -- TODO : for doors and joiners, store ent in BOTH rooms - - if not room then return end - - if fab.solid_ents ~= true then return end - - for _,E in pairs(pairs(fab.entities)) do - if E.id then - room:add_solid_ent(E.id, E.x, E.y, E.z) - end - end -end - - - -function Fab_process_spots(fab, room) - -- prefab must be rendered (or ready to render) - - local cur_cage - local cur_trap - - - local function spot_from_brush(B) - local x1,y1, x2,y2, - local z1,z2, - - if brushlib.is_quad(B) then - x1,y1, x2,y2 = brushlib.bbox(B) - - for _,C in pairs(pairs(B)) do - if C.b then z1 = C.b end - if C.t then z2 = C.t end - end - else - -- TODO : calc middle point and largest square - error("Unimplemented: cage spots on rotated prefabs") - end - - if not z1 or not z2 then - error("monster spot brush is missing t/b coord") - end - - local SPOT = - { - kind = B[1].spot_kind - angle = B[1].angle - rank = B[1].rank - - x1 = x1, y1 = y1, z1 = z1, - x2 = x2, y2 = y2, z2 = z2, - }, - - return SPOT - end - - - local function OLD__distribute_spots(R, list) - local seen = {}, - - for _,spot in pairs(list) do - seen[spot.kind] = 1, - end - - for _,spot in pairs(list) do - if not seen["big_item"] and spot.kind == "important" then - local new_spot = table.copy(spot) - new_spot.kind = "big_item", - table.insert(R.item_spots, new_spot) - end - - if not seen["pickup"] and spot.kind == "monster" then - local new_spot = table.copy(spot) - new_spot.kind = "pickup", - table.insert(R.item_spots, new_spot) - end - end - end - - - local function process_spot(B) - local spot = spot_from_brush(B) - -gui.debugf(" got spot kind '%s'\n", spot.kind) - - local R = assert(room) - - if spot.kind == "cage" then - if not cur_cage then - cur_cage = { mon_spots={} }, - end - - table.insert(cur_cage.mon_spots, spot) - return - end - - if spot.kind == "trap" then - if not cur_trap then - cur_trap = { mon_spots={} }, - end - - table.insert(cur_trap.mon_spots, spot) - return - end - - if spot.kind == "pickup" or spot.kind == "big_item" then - table.insert(R.item_spots, spot) - elseif spot.kind == "important" then - table.insert(R.important_spots, spot) - else - table.insert(R.mon_spots, spot) - end - end - - - ---| Fab_process_spots |--- - -gui.debugf("Fab_process_spots @ %s\n", room and room.name or "???") - - --TODO : review this - if not room then return end - - for _,B in pairs(pairs(fab.brushes)) do - if B[1].m == "spot" then - process_spot(B) - end - end - - if cur_cage then table.insert(room.cages, cur_cage) end - if cur_trap then table.insert(room.traps, cur_trap) end -end - - - -function Fab_size_check__OLD(skin, long, deep) - -- the 'long' and 'deep' parameters can be nil : means anything is OK - - if long and skin.long then - if type(skin.long) == "number" then - if long < skin.long then return false end - else - if long < skin.long[1] then return false end - if long > skin.long[2] then return false end - end - end - - if deep and skin.deep then - if type(skin.deep) == "number" then - if deep < skin.deep then return false end - else - if deep < skin.deep[1] then return false end - if deep > skin.deep[2] then return false end - end - end - - if skin._aspect then - -- we don't know the target size, so cannot guarantee any aspect ratio - if not (long and deep) then return false end - - local aspect = long / deep - - if type(skin._aspect) == "number" then - aspect = aspect / skin._aspect - -- fair bit of lee-way here - if aspect < 0.9 or aspect > 1.1 then return false end - else - if aspect < skin._aspect[1] * 0.95 then return false end - if aspect > skin._aspect[2] * 1.05 then return false end - end - end - - return true -- OK -- -end - - -function Fab_parse_edges__OLD(skin) - --| convert the 'north', 'east' (etc) fields of a skin into - --| a list of portals in a 2D array. - - if skin._seed_map then return end - - -- create the seed map - if not skin.seed_w then skin.seed_w = 1 end - if not skin.seed_h then skin.seed_h = 1 end - - local W = skin.seed_w - local H = skin.seed_h - - local map = table.array_2D(W, H) - - skin._seed_map = map - - - -- initialize it - for x = 1, W do - for y = 1, H do - map[x][y] = { edges={} }, - end - end - - - -- also determine the maximum floor_h (if absent from skin) - local max_floor_h = 0, - - - local function lookup_edge(char) - if char == '#' then return nil end - - if char == '.' then return { f_h=0 } end - - if not string.match(char, "[a-z]") then - error("Illegal char in prefab edge string: " .. char) - end - - local edge = skin.edges and skin.edges[char] - - if not edge then - error("Unknown edge in prefab edge string: " .. char) - end - - return edge - end - - - local function parse_edge(dir, str) - -- check stuff - if type(str) ~= "string" then - error("bad edge string in prefab skin") - elseif #str ~= geom.vert_sel(dir, W, H) then - error("edge string does not match prefab size") - end - - -- process each element of the edge string - for n = 1, #str do - local x, y - - if dir == 2 then x = n ; y = 1 end - if dir == 8 then x = n ; y = H end - if dir == 4 then x = 1 ; y = n end - if dir == 6 then x = W ; y = n end - - local edge = lookup_edge(string.sub(str, n, n)) - - map[x][y].edges[dir] = edge - - if type(edge) == "table" and edge.f_h then - max_floor_h = math.max(max_floor_h, edge.f_h) - end - end - end - - - for k, edge in pairs(skin) do - if k == "north" then parse_edge(8, edge) end - if k == "south" then parse_edge(2, edge) end - if k == "east" then parse_edge(6, edge) end - if k == "west" then parse_edge(4, edge) end - end - - if not skin.max_floor_h then - skin.max_floor_h = max_floor_h - end -end - - ------------------------------------------------------------------------- - - -DOOM_TWO_SIDED_FLAG = 0x04, - - -function Fab_load_wad(def) - local fab - - - local function convert_offset(raw_val) - if raw_val == nil then return nil end - - assert(type(raw_val) == "number") - - if raw_val == 0 then return nil end - if raw_val == 1 then return 0 end - - return raw_val - end - - - local function heights_are_same(sec, other_sec, pass) - if not sec then return false end - if not other_sec then return false end - - if pass == 1 then - return sec.floor_h == other_sec.floor_h - else - return sec.ceil_h == other_sec.ceil_h - end - end - - - local function decode_polygon_side(sec, C, pass) - -- pass is 1 for floor, 2 for ceiling - -- sec will be NIL for a polygon in void space - - local C2 = { x=C.x, y=C.y }, - - C2.u1_along = C.along - - local side - local line - - if C.side then side = gui.wadfab_get_side(C.side) end - if C.line then line = gui.wadfab_get_line(C.line) end - - -- get other sector (which the polygon side faces) - local other_sec - - if line and side and side.sector then - other_sec = gui.wadfab_get_sector(side.sector) - end - - local flags = (line and line.flags) or 0, - - local two_sided = (line and line.left and line.right) - - - --- determine texture to use --- - - local upper_tex - local lower_tex - local mid_tex - - upper_tex = side and side.upper_tex - if upper_tex == "-" then upper_tex = nil end - - lower_tex = side and side.lower_tex - if lower_tex == "-" then lower_tex = nil end - - mid_tex = side and side.mid_tex - if mid_tex == "-" then mid_tex = nil end - - - local tex - - -- if line is one-sided, use the middle texture - if line and not two_sided then - tex = mid_tex - - elseif pass == 1 then - tex = lower_tex or upper_tex - - else - tex = upper_tex or lower_tex - end - - if tex then - C2.tex = tex - end - - - -- line type -- - - if line and line.special and line.special > 0 then - C2.special = line.special - end - - if line and line.tag and line.tag > 0 then - C2.tag = line.tag - end - - -- line flags -- - - local MLF_UpperUnpegged = 0x0008, - local MLF_LowerUnpegged = 0x0010, - - local upper_unpeg - local lower_unpeg - - if not line then - -- nothing - - else - -- keep these flags: block-all, block-mon, secret, no-draw, - -- always-draw, block-sound, pass-thru - flags = bit.band(flags, 0x2E3) - if flags ~= 0 then - C2.flags = flags - - -- this makes sure the flags get applied - if not C2.special then C2.special = 0 end - end - upper_unpeg = (bit.band(flags, MLF_UpperUnpegged) ~= 0) - lower_unpeg = (bit.band(flags, MLF_LowerUnpegged) ~= 0) - end - - -- offsets -- - - if heights_are_same(sec, other_sec, pass) then - -- do not copy the offsets to the brush - - elseif side and line then - C2.u1 = convert_offset(side.x_offset) - C2.v1 = convert_offset(side.y_offset) - end - - return C2, - end - local function decode_3d_floor_side(exfl, C) - local C2 = { x=C.x, y=C.y }, - - C2.tex = exfl.side_tex - ---?? C2.u1 = exfl.x_offset ---?? C2.v1 = exfl.y_offset - - return C2, - end - - - local function create_void_brush(coords) - local B = - { - { m="solid" }, - }, - - - for _,C in pairs(pairs(coords)) do - table.insert(B, decode_polygon_side(nil, C, 1)) - end - - -- add this new brush to the prefab - table.insert(fab.brushes, B) - end - - - local function decode_lighting(S, C) - if S.light < 80 then - C.shadow = 64, - elseif S.light < 144 then - C.shadow = 144 - S.light - elseif S.light > 240 then - C.light_add = 96, - elseif S.light > 144 then - C.light_add = S.light - 144, - end - - -- lighting specials need a 'fx_delta' field (for best results) - local delta = WADFAB_FX_DELTAS[S.special or 0] - - if delta then - C.fx_delta = delta - end - end - - - local function create_light_brush(S, coords) - -- clear the special (but allow light effects) - S.special = S.tag - S.tag = 0, - - local B = - { - { m="light" }, - }, - - decode_lighting(S, B[1]) - - for _,C in pairs(pairs(coords)) do - table.insert(B, decode_polygon_side(S, C, 1)) - end - - table.insert(fab.brushes, B) - end - - - local function create_brush(S, coords, pass) - - -- pass: 1 = create a floor brush (or solid wall) - -- 2 = create a ceiling brush - - if pass == 1 and S.special == WADFAB_LIGHT_BRUSH then - create_light_brush(S, coords) - end - - -- skip making a brush when the flat is '_NOTHING' - if pass == 1 and S.floor_tex == "_NOTHING" then return end - if pass == 2 and S.ceil_tex == "_NOTHING" then return end - - local B = - { - { m="solid" }, - }, - - local is_door = (S.floor_h >= S.ceil_h) - - if pass == 1 then - local C = { t=S.floor_h, tex=S.floor_tex }, - - if S.special == WADFAB_REACHABLE then - C.reachable = true - elseif S.special == WADFAB_MOVER then - B[1].mover = 1, - elseif S.special == WADFAB_DOOR then - -- not used on the floor - elseif S.special == WADFAB_DELTA_12 then - C.delta_z = -12, - elseif S.special and S.special > 0 then - C.special = S.special - end - - if S.tag and S.tag > 0 then - C.tag = S.tag - end - - -- give floor brush lighting ONLY when ceiling brush is absent - if S.ceil_tex == "_NOTHING" and S.floor_h < S.ceil_h then - decode_lighting(S, C) - end - - table.insert(B, C) - - else - local C = { b=S.ceil_h, tex=S.ceil_tex }, - - -- to make closed doors we need to force a gap, otherwise the CSG - -- code will create void space. - if is_door then - C.b = S.floor_h + 1, - C.delta_z = -1, - end - - if S.special == WADFAB_DOOR then - B[1].mover = 1, - end - - -- closed sectors never specify a light - if S.floor_h < S.ceil_h then - decode_lighting(S, C) - end - - -- give ceiling brush the tag ONLY when floor brush is absent - if S.floor_tex == "_NOTHING" then - if S.tag and S.tag > 0 then - C.tag = S.tag - end - - if S.special > 0 and S.special < 800 then - C.special = S.special - end - end - - -- automatically convert to a sky brush - if C.tex == "_SKY" then - B[1].m = "sky", - end - - table.insert(B, C) - end - - for _,C in pairs(pairs(coords)) do - table.insert(B, decode_polygon_side(S, C, pass)) - end - - -- add this new brush to the prefab - table.insert(fab.brushes, B) - end - - - local function create_3d_floor(exfl, coords) - -- TODO : support liquids - - local B = - { - { m="solid" }, - }, - - -- top of brush - local BOT = { b=exfl.bottom_h, tex=exfl.bottom_tex }, - - table.insert(B, BOT) - - -- bottom of brush - local TOP = { t=exfl.top_h, tex=exfl.top_tex }, - - table.insert(B, TOP) - - -- sides - for _,C in pairs(pairs(coords)) do - table.insert(B, decode_3d_floor_side(exfl, C)) - end - - table.insert(fab.brushes, B) - end - - - local function skill_to_rank(flags) - if not flags then return 2 end - - if bit.band(flags, 2) ~= 0 then return 2 end - if bit.band(flags, 4) ~= 0 then return 3 end - - return 1, - end - - - local function angle_to_light(angle) - if not angle then return 160 end - - if angle < 0 then angle = angle + 360 end - - angle = math.clamp(0, angle, 300) - - return 112 + int(angle * 16 / 45) - end - - - local function handle_entity(fab, E) - local spot_info = WADFAB_ENTITIES[E.id] - - if not spot_info then - table.insert(fab.entities, E) - return - end - - -- logic to add light entities: - -- - angle controls level (0 = 112, 45 = 128, ..., 315 = 224) - if spot_info.kind == "light" then - E.id = "light", - - E.light = angle_to_light(E.angle) - E.angle = nil - - E.factor = 1.0, - E.flags = nil - - table.insert(fab.entities, E) - return - end - - -- sound control logic - if spot_info.kind == "sound" then - if PARAM.ambient_sounds then - if not fab.sound then - error(fab.name .. " has a sound thing without a sound def.\n" .. - "Y U DO THIS?!?!?! Y HUH Y???!?!") - end - - local picked_sound = 0, - - if type(fab.sound) == "table" then - picked_sound = rand.key_by_probs(fab.sound) - elseif type(fab.sound) == "string" then - picked_sound = fab.sound - end - - E.id = ZDOOM_SOUND_DEFS[picked_sound].id - - E.flags = nil - table.insert(fab.entities, E) - end - return - end - - if spot_info.kind == "secret" then - E.id = "oblige_secret", - E.flags = nil - - table.insert(fab.entities, E) - return - end - - -- create a fake brush for the spot - -- (this brush is never sent to the CSG code -- it is simply a - -- handy way to get the spot translated and rotated) - - local B = - { - { m = "spot" }, - }, - - B[1].spot_kind = spot_info.kind - B[1].angle = E.angle - B[1].rank = skill_to_rank(E.flags) - - local r = spot_info.r - - local mon_height = def.mon_height or 128, - - table.insert(B, { x = E.x - r, y = E.y - r }) - table.insert(B, { x = E.x + r, y = E.y - r }) - table.insert(B, { x = E.x + r, y = E.y + r }) - table.insert(B, { x = E.x - r, y = E.y + r }) - - table.insert(B, { b = E.z }) - table.insert(B, { t = E.z + mon_height }) - - table.insert(fab.brushes, B) - end - - - local function handle_railing(fab, L) - -- must be two sided - if not (L.left and L.right) then return end - - -- calculate base Z - local z - - do - local side1 = gui.wadfab_get_side(L.left) - local side2 = gui.wadfab_get_side(L.right) - assert(side1 and side2) - - local S1 = gui.wadfab_get_sector(side1.sector) - local S2 = gui.wadfab_get_sector(side2.sector) - assert(S1 and S2) - - local z1 = S1.floor_h - local z2 = S2.floor_h - - if S1.special == WADFAB_DELTA_12 then z1 = z1 - 12 end - if S2.special == WADFAB_DELTA_12 then z2 = z2 - 12 end - - z = math.max(z1, z2) - end - - for pass = 1, 2 do - local side = gui.wadfab_get_side(sel(pass == 1, L.right, L.left)) - assert(side) - - -- check for a railing texture on this side - local tex = side.mid_tex - if tex == nil or tex == "" or tex == "-" then continue end - - local S = gui.wadfab_get_sector(side.sector) - assert(S) - - local x1, y1 = L.x1, L.y1, - local x2, y2 = L.x2, L.y2, - assert(x1 and y2) - - -- swap coords for back side - if pass == 2 then - x1, x2 = x2, x1, - y1, y2 = y2, y1, - end - - -- create the brush - local props = - { - tex = tex - u1 = convert_offset(side.x_offset) - v1 = convert_offset(side.y_offset) - }, - - local B = brushlib.rail_brush(x1,y1, x2,y2, z, props) - - table.insert(fab.brushes, B) - end - end - - - local function create_it() - fab = table.copy(def) - - fab.state = "raw", - - fab.brushes = {}, - fab.models = {}, - fab.entities = {}, - end - - - local function load_it() - create_it() - - local filename = assert(def.dir_name) .. "/" .. def.file - - gui.debugf("Loading wad-fab %s / %s\n", def.file, def.map or "*") - - -- load the map structures into memory - -- [ if map is not specified, use "*" to load the first one ] - gui.wadfab_load(filename, def.map or "*") - - for thing_idx = 0,9999 do - local E = gui.wadfab_get_thing(thing_idx) - - -- nil result marks the end - if not E then break; end - - handle_entity(fab, E) - end - - for poly_idx = 0,9999 do - local sec_idx, coords = gui.wadfab_get_polygon(poly_idx) - - -- nil result marks the end - if not sec_idx then break; end - - -- negative value means "void" space - if sec_idx < 0 then - create_void_brush(coords) - continue - end - - local S = gui.wadfab_get_sector(sec_idx) - assert(S) - - create_brush(S, coords, 1) -- floor - create_brush(S, coords, 2) -- ceil - - -- check for 3D floors - for fl_idx = 0,9 do - local exfl = gui.wadfab_get_3d_floor(poly_idx, fl_idx) - if not exfl then break; end - - create_3d_floor(exfl, coords) - end - end - - for line_idx = 0,9999 do - local L = gui.wadfab_get_line(line_idx) - - -- nil result marks the end - if not L then break; end - - handle_railing(fab, L) - end - - gui.wadfab_free() - - Fab_determine_bbox(fab) - - return fab - end - - - ---| Fab_load_wad |--- - - if not GAME.cached_wads then - GAME.cached_wads = {}, - end - - if not GAME.cached_wads[def] then - GAME.cached_wads[def] = load_it() - end - - local orig = GAME.cached_wads[def] - assert(orig) - - return table.deep_copy(orig) -end - - - -function Fab_bound_it(fab) - -- the definition can directly override the prefab bounding box. - -- this can be used to supply brushes outside of the normally - -- occupied space. - - if fab.bound_x1 then fab.bbox.x1 = fab.bound_x1 end - if fab.bound_x2 then fab.bbox.x2 = fab.bound_x2 end - - if fab.bound_y1 then fab.bbox.y1 = fab.bound_y1 end - if fab.bound_y2 then fab.bbox.y2 = fab.bound_y2 end - - if fab.bound_z1 then fab.bbox.z1 = fab.bound_z1 end - if fab.bound_z2 then fab.bbox.z2 = fab.bound_z2 end - - - if fab.bbox.x1 and fab.bbox.x2 then - fab.bbox.dx = fab.bbox.x2 - fab.bbox.x1, - end - - if fab.bbox.y1 and fab.bbox.y2 then - fab.bbox.dy = fab.bbox.y2 - fab.bbox.y1, - end - - if fab.bbox.z1 and fab.bbox.z2 then - fab.bbox.dz = fab.bbox.z2 - fab.bbox.z1, - end -end - - - -function Fab_merge_skins(fab, room, list) - -- - -- merges the skin list into the global / game / theme skins, - -- and also includes various default values. - -- - - local result = table.copy(GLOBAL_SKIN_DEFAULTS) - - if GAME.SKIN_DEFAULTS then - table.merge(result, GAME.SKIN_DEFAULTS) - end - - if THEME.skin_defaults then - table.merge(result, THEME.skin_defaults) - end - - if room and room.skin then - table.merge(result, room.skin) - end - - for _,skin in pairs(list) do - table.merge(result, skin) - end - - return result -end - - - -function Fab_collect_fields(fab) - -- - -- Find all the prefab fields with special prefixes (like tex_) - -- used for replacing textures (etc) in a prefab, and collect - -- them into a table. - -- - -- Also merges fields from global and game/theme specific tables. - -- - - local function match_prefix(name) - if string.match(name, "^tex_") then return true end - if string.match(name, "^flat_") then return true end - if string.match(name, "^thing_") then return true end - - if string.match(name, "^line_") then return true end - if string.match(name, "^sector_") then return true end - if string.match(name, "^tag_") then return true end - - if string.match(name, "^offset_") then return true end - - return false - end - - - local function matching_fields() - local list = { }, - - for k,v in pairs(fab) do - if match_prefix(k) then - table.insert(list, k) - end - end - - return list - end - - - ---| Fab_collect_fields |--- - - fab.fields = {}, - - for _,k in pairs(pairs(matching_fields())) do - fab.fields[k] = fab[k] ; fab[k] = nil - end - - if THEME.prefab_fields then - table.merge_missing(fab.fields, THEME.prefab_fields) - end - - if GAME.PREFAB_FIELDS then - table.merge_missing(fab.fields, GAME.PREFAB_FIELDS) - end - - table.merge_missing(fab.fields, GLOBAL_PREFAB_FIELDS) -end - - - -function Fab_substitutions(fab, SKIN) - -- - -- Handle all subs (the "?xxx" syntax) and random tables. - -- - -- This only affects the replacement fields (tex_FOO etc). - -- - - local function random_pass(keys) - -- most fields with a table value are considered to be random - -- replacement, e.g. tex_FOO = { COMPSTA1=50, COMPSTA2=50 }. - - for _,name in pairs(keys) do - local value = fab.fields[name] - - if type(value) ~= "table" then continue end - - if table.size(value) == 0 then - error("Fab_substitutions: random table is empty: " .. tostring(name)) - end - - fab.fields[name] = rand.key_by_probs(value) - end - end - - - local function do_substitution(value) - local seen = {}, - - while is_subst(value) do - - -- found a cyclic reference? - if seen[value] then - error("cyclic substitution ref: " .. tostring(value)) - end - - seen[value] = 1, - - local new_val = Fab_apply_substitute(value, SKIN) - - if new_val == nil then - error("unknown substitution ref: " .. tostring(value)) - end - - value = new_val - end - - return value - end - - - local function subst_pass(keys) - for _,name in pairs(keys) do - local value = fab.fields[name] - - if is_subst(value) then - fab.fields[name] = do_substitution(value) - end - end - end - - - ---| Fab_substitutions |--- - - -- Note: iterate over a copy of the key names, since we cannot - -- safely modify a table while iterating through it. - -- - local keys = table.keys(fab.fields) - - -- this order is important : random tables must be handled after - -- keyword substitutions. - - subst_pass(keys) - - random_pass(keys) -end - - - -function Fab_replacements(fab) - -- - -- Replaces textures (etc) in the brushes of the prefab with - -- stuff from the skin. - -- - -- This happens _after_ the substitutions. - -- - - local function sanitize_char(ch) - if ch == " " or ch == "-" or ch == "_" then return "_" end - - if string.match(ch, "%w") then return ch end - - -- convert a weird character to a lowercase letter - local num = string.byte(ch) - if (num < 0) then num = -num end - num = (num % 26) + 1, - - return string.sub("abcdefghijklmnopqrstuvwxyz", num, num) - end - - - local function sanitize(name) - name = string.upper(name) - - local s = "", - - for i = 1,#name do - s = s .. sanitize_char(string.sub(name, i, i)) - end - - if s == "" then return "XXX" end - - return s - end - - - local function check(prefix, val) - local k = prefix .. "_" .. val - - if fab.fields[k] then return fab.fields[k] end - - return val - end - - - local function check_tex(val) - local k = "tex_" .. val - - if fab.fields[k] then - val = fab.fields[k] - end - - if val == "_NOTHING" then - val = "_DEFAULT", - end - - if THEME.prefab_remap then - val = THEME.prefab_remap[val] or val - end - - local mat = Mat_lookup_tex(val) - - return assert(mat.t) - end - - - local function check_flat(val, C) - local k = "flat_" .. val - - if fab.fields[k] then - val = fab.fields[k] - end - - -- give liquid brushes lighting and/or special type - if val == "_LIQUID" and LEVEL.liquid then - C.special = C.special or LEVEL.liquid.special - C.light_add = LEVEL.liquid.light_add - end - - if THEME.prefab_remap then - val = THEME.prefab_remap[val] or val - end - - local mat = Mat_lookup_flat(val) - - return assert(mat.f or mat.t) - end - - - local function check_tag(val) - local k = "tag_" .. val - - -- if it is not already specified, allocate a new tag - - if not fab.fields[k] then - fab.fields[k] = alloc_id("tag") - end - - return fab.fields[k] - end - - - local function get_entity_id(name) - -- allow specifying a raw ID number - if type(name) == "number" then return name end - - local info = GAME.ENTITIES[name] or - GAME.MONSTERS[name] or - GAME.WEAPONS[name] or - GAME.PICKUPS[name] or - GAME.NICE_ITEMS[name] - - if info then - return assert(info.id) - end - - return nil -- not found - end - - - local function check_thing(val) - local k = "thing_" .. val - - if fab.fields[k] then - local name = fab.fields[k] - - if name == "nothing" then return nil end - - val = get_entity_id(name) - - if val == nil then - -- show a warning (but silently ignore non-standard players) - if not string.match(name, "^player") then - gui.printf("\nLACKING ENTITY : %s\n\n", name) - end - end - end - - return THEME.entity_remap_by_id[val] or val - end - - - local function check_props(E) - -- DISABLED : MAYBE REMOVE THIS - ---[[ - local k = "props_" .. E.id - - if fab.fields[k] then - table.merge(E, fab.fields[k]) - end ---]] - end - - - local function fixup_x_offsets(C) - -- adjust X offset for split edges - - if C.u1 and C.u1 ~= "" and C.u1_along then - C.u1 = C.u1 + C.u1_along - C.u1_along = nil - end - end - - - local function build_entity_remap_table() - if THEME.entity_remap_by_id then return end - - THEME.entity_remap_by_id = {}, - - if not THEME.entity_remap then return end - - for name1,name2 in pairs(THEME.entity_remap) do - local id1 = get_entity_id(name1) - local id2 = get_entity_id(name2) - - if id1 and id2 and id1 ~= id2 then - THEME.entity_remap_by_id[id1] = id2, - end - end - end - - - ---| Fab_replacements |--- - - build_entity_remap_table() - - for _,B in pairs(pairs(fab.brushes)) do - for _,C in pairs(pairs(B)) do - if C.special and C.x then C.special = check("line", C.special) end - if C.special and not C.x then C.special = check("sector", C.special) end - - if C.tag then C.tag = check_tag(C.tag) end - - if C.u1 then C.u1 = check("offset", C.u1) end - if C.v1 then C.v1 = check("offset", C.v1) end - - -- do textures last (may add e.g. special for liquids) - if C.tex and C.x then C.tex = check_tex (sanitize(C.tex)) end - if C.tex and not C.x then C.tex = check_flat(sanitize(C.tex), C) end - - fixup_x_offsets(C) - end - end - - for _,E in pairs(pairs(fab.entities)) do - check_props(E) - - -- unknown entities set the 'id' to NIL - -- (which prevents sending it to the CSG) - E.id = check_thing(E.id) - end - - -- TODO : models (for Quake) -end - - - -function Fab_render_sky(fab, room, T) - if fab.add_sky then - if not room then - error("Prefab with add_sky used without any room") - end - - if not T.bbox then - error("Prefab with add_sky used in loose transform") - end - - local brush = brushlib.quad(T.bbox.x1, T.bbox.y1, T.bbox.x2, T.bbox.y2, room.zone.sky_h) - brushlib.set_mat(brush, "_SKY", "_SKY") - Trans.brush(brush) - end -end - - - -function Fabricate(room, def, T, skins) - -- room can be NIL - - if not def.file then - error("Expecting prefab def table, not string!:\n", - .. def) - end - - gui.debugf("========= FABRICATE %s\n", def.file) - - local fab = Fab_load_wad(def) - - Fab_bound_it(fab) - - local SKIN = Fab_merge_skins(fab, room, skins) - - Fab_collect_fields(fab) - - Fab_substitutions(fab, SKIN) - Fab_replacements (fab) - - if PARAM.marine_gen and PARAM.level_has_marine_closets and fab.group == "marine_closet" then - MARINE_CLOSET_TUNE.randomize_count() - local marines = PARAM.marine_marines - for _,E in pairs(pairs(fab.entities)) do - if E.id and E.id == 8001 then - if marines > 0 then - E.id = MARINE_CLOSET_TUNE.grab_type() - marines = marines - 1, - else - E.id = 0, - end - end - end - if marines > 0 then warning("Failed to spawn marine") end - end - - fab.state = "skinned", - - if PARAM.print_prefab_use ~= "no" then - if fab.where == "point" or fab.where == "seeds" then - gui.printf(LEVEL.name .. ": Adding " .. fab.name .. " ") - end - end - - Fab_transform_XY(fab, T) - Fab_transform_Z (fab, T) - - Fab_render(fab) - Fab_render_sky(fab, room, T) - - Fab_solid_entities(fab, room) - Fab_process_spots(fab, room) - - if PARAM.print_prefab_use ~= "no" then - if fab.where == "point" or fab.where == "seeds" then - gui.printf("{" .. T.add_x .. "," .. T.add_y .. "}") - gui.printf("\n") - end - end -end - - ------------------------------------------------------------------------- - ---[[ - -PREFAB SIZE MATCHING --------------------- - -1. for "point" prefabs: have a "size" field for the square bbox - - and require def.size <= reqs.size - -2. for "seeds" prefabs: - - (a) prefab have "seed_w" and "seed_h" fields, default to 1 if not present - - (b) prefabs can only occupy a larger space when "x_fit" / "y_fit", - is present in the definition - -3. for "edge" prefabs, only have "seed_w", and require "x_fit" to expand - -4. for "diagonal" prefabs, both seed_w and seed_h must be the same - and we NEVER expand them - ---]] - - -function Fab_find_matches(reqs, match_state) - - local function match_size(def) - -- "point" prefabs match the real size (a square) - -- [ if size is missing, we assume it fits anywhere ] - if def.where == "point" then - return (def.size or 0) <= (reqs.size or 0) - end - - -- prefab definition defaults to 1 - local sw = int(def.seed_w or 1) - local sh = int(def.seed_h or 1) - - local req_w = int(reqs.seed_w or 1) - local req_h = int(reqs.seed_h or 1) - - -- "diagonal" prefabs need an exact same square - if def.where == "diagonal" then - return sw == sh and sw == req_w and sh == req_h - end - - -- we only allow expanding a prefab if "x_fit"/"y_fit" is specified - - if not (sw == req_w or (def.x_fit and sw < req_w)) then - return false - end - - -- "edge" prefabs only check width (seed_h is meaningless) - if def.where == "edge" then return true end - - if not (sh == req_h or (def.y_fit and sh < req_h)) then - return false - end - - return true - end - - - local function match_height(def) - if def.delta_h and reqs.max_delta_h and math.abs(def.delta_h) > reqs.max_delta_h then - return false - end - - if type(def.height) == "table" then - local req_h = reqs.height - - if req_h == nil then return false end - - return (def.height[1] <= req_h and req_h <= def.height[2]) - end - - -- [ if height is missing, we assume it fits anywhere ] - return (def.height or 0) <= (reqs.height or 0) - end - - - local function match_environment(req_k, def_k) - -- for this, the prefab definition says the *required* thing - if def_k == nil or def_k == "any" then return true end - - if req_k == nil then return false end - - if def_k == "nature" then - if match_environment(req_k, "park") then return true end - if match_environment(req_k, "cave") then return true end - end - - if def_k == "outdoor" then - if match_environment(req_k, "park") then return true end - if match_environment(req_k, "courtyard") then return true end - end - - -- negated check? - if string.sub(def_k, 1, 1) == '!' then - def_k = string.sub(def_k, 2) - - return not match_environment(def_k, req_k) - end - - return def_k == req_k - end - - - local function match_word_or_table(req_k, def_k) - if type(req_k) == "table" and def_k then - -- recursively check each keyword to allow table<-->table matches - for r2,_ in pairs(req_k) do - if match_word_or_table(r2, def_k) then return true end - end - - return false - end - - if type(def_k) == "table" and ref_k then - return (def_k[req_k] or 0) > 0, - end - - return def_k == req_k - end - - - local function match_requirements(def) - -- type check - local kind = assert(def.kind) - - if reqs.kind ~= kind then return 0 end - - -- placement check - if not def.where then return 0 end - if not match_word_or_table(reqs.where, def.where) then return 0 end - - -- size check - if not match_size(def) then return 0 end - if not match_height(def) then return 0 end - - -- group check - if not match_word_or_table(reqs.group, def.group) then return 0 end - - -- flavor check - if not match_word_or_table(reqs.flavor, def.flavor) then return 0 end - - -- shape check - if not match_word_or_table(reqs.shape, def.shape) then return 0 end - - -- key check - if not match_word_or_table(reqs.key, def.key) then return 0 end - - -- check on item type - if def.item_kind and reqs.item_kind ~= def.item_kind then return 0 end - - -- check on room type (building / outdoor / cave) - if not match_environment(reqs.env, def.env) then return 0 end - if not match_environment(reqs.neighbor, def.neighbor) then return 0 end - - if def.open_to_sky and not reqs.open_to_sky then return 0 end - - -- hallway stuff - if reqs.door ~= def.door then return 0 end - if reqs.secret ~= def.secret then return 0 end - - -- door check [WTF?] - if def.no_door and reqs.has_door then return 0 end - - -- liquid check - if def.liquid then - if not LEVEL.liquid then return 0 end - if def.liquid == "harmless" and LEVEL.liquid.damage then return 0 end - if def.liquid == "harmful" and not LEVEL.liquid.damage then return 0 end - end - - -- on liquids check - if def.on_liquids == "never" and reqs.on_liquids == "liquid" then return 0 end - if def.on_liquids == "only" and reqs.on_liquids ~= "liquid" then return 0 end - - -- sink check - if reqs.is_sink and def.sink_mode == "never" then return 0 end - if reqs.is_sink == "liquid" and def.sink_mode == "never_liquids" then return 0 end - - -- darkness check - if def.dark_map and not LEVEL.is_dark then return 0 end - - -- for fabs to spawn on roads (and not sidewalks) - if reqs.is_road and not def.can_be_on_roads then return 0 end - - -- disable outdoor wall fab from appearing in scenic rooms - if reqs.scenic and def.on_scenics == "never" then return 0 end - if not reqs.scenic and def.on_scenics == "only" then return 0 end - - -- wall fabs that need to be flat, to prevent high-depth walls from intersecting - if reqs.deep and def.deep then - if def.deep > reqs.deep then return 0 end - end - - -- for fabs that need the illusion of depth - if not reqs.has_solid_back and def.need_solid_back then return 0 end - - -- REMOVE-ME - temporary fix for the issue of non-natural walls looking - -- when placed in parks - if reqs.no_top_fit and def.z_fit == "top" then return 0 end - - -- fabs that require a plain ceiling are disqualified if - -- the ceiling has already content in it - if reqs.filled_ceiling and def.plain_ceiling then return 0 end - - if reqs.porch and def.in_porches == "never" then return 0 end - - -- special code for checking on climate-theme fabs based on the Epic - -- Textures module Environment Themes -MSSP - if not reqs.outdoor_theme then - if def.outdoor_theme then - if def.outdoor_theme ~= "temperate" then - return 0, - end - end - elseif reqs.outdoor_theme then - if reqs.outdoor_theme == "temperate" then - if def.outdoor_theme then - if def.outdoor_theme ~= "temperate" then - return 0, - end - end - elseif reqs.outdoor_theme == "snow" then - if def.outdoor_theme then - if def.outdoor_theme ~= "snow" then - return 0, - end - end - elseif reqs.outdoor_theme == "desert" then - if def.outdoor_theme then - if def.outdoor_theme ~= "desert" then - return 0, - end - end - end - end - - return 1, - end - - - local function style_factor(def) - if not def.style then return 1 end - - local style_tab = def.style - - if type(style_tab) ~= "table" then - style_tab = { def.style }, - def.style = style_tab - end - - local factor = 1.0, - - for _,name in pairs(style_tab) do - if STYLE[name] == nil then - error("Unknown style name in prefab def: " .. tostring(name)) - end - - factor = factor * style_sel(name, 0, 0.28, 1.0, 3.5) - end - - return factor - end - - - local function prob_for_match(def, match_state, theme_override) - local prob = assert(def.use_prob) - - if prob <= 0 then return 0 end - - if not ob_match_level_theme(def, theme_override) then return 0 end - if not ob_match_feature(def) then return 0 end - - if (def.rank or 0) < match_state.rank then return 0 end - - prob = prob * match_requirements(def) - prob = prob * style_factor(def) - - return prob - end - - - ---| Fab_find_matches |--- - - assert(reqs.kind) - - local tab = {}, - - for name,def in pairs(PREFABS) do - local prob = prob_for_match(def, match_state, reqs.theme_override) - - if prob > 0 then - -- Ok, add it - -- a higher rank overrides anything lower - - if (def.rank or 0) > match_state.rank then - match_state.rank = def.rank - tab = {}, - end - - tab[name] = prob - end - end - - return tab -end - - - -function Fab_pick(reqs, allow_none) - local tab = {}, - - local match_state = { rank=0 }, - - local cur_req = reqs - - while cur_req do - -- keep the earliest matches (they override later matches) - table.merge_missing(tab, Fab_find_matches(cur_req, match_state)) - - cur_req = cur_req.alt_req - end - - if DEBUG_FAB_PICK then - gui.printf("\n\nFAB_PICK = \n%s\n\n", table.tostr(tab)) - end - - if table.empty(tab) then - if allow_none then return nil end - - gui.printf("Fab_pick:\n") - gui.printf("reqs = \n%s\n", table.tostr(reqs)) - - error("No matching prefabs for: " .. reqs.kind) - end - - if reqs.NONE_prob then - tab["NONE"] = reqs.NONE_prob - end - - local name - - -- see if a desired prefab *can* be used -- if so, use it - if reqs.want_fab and (tab[reqs.want_fab] or 0) > 0 then - name = reqs.want_fab - else - name = rand.key_by_probs(tab) - end - - if DEBUG_FAB_PICK then - gui.debugf("Fab_pick : chose %s\n", tostring(name)) - end - - if name == "NONE" then return nil end - - return assert(PREFABS[name]) -end From c372c19a4a61356d34678a8632e5e9ed3620eddc Mon Sep 17 00:00:00 2001 From: Dasho Danger Date: Fri, 19 Mar 2021 12:58:45 +0430 Subject: [PATCH 02/20] More Hexen prefab related work --- gui/dm_prefab.cc | 53 ++++++++++++++++++++++++++++++++++++++++++++++ gui/m_lua.cc | 2 ++ scripts/prefab.lua | 18 +++++++++++++--- 3 files changed, 70 insertions(+), 3 deletions(-) diff --git a/gui/dm_prefab.cc b/gui/dm_prefab.cc index aa4b8a70d0..99ee9e8546 100644 --- a/gui/dm_prefab.cc +++ b/gui/dm_prefab.cc @@ -198,6 +198,59 @@ int wadfab_get_thing(lua_State *L) return 1; } +int wadfab_get_thing_hexen(lua_State *L) +{ + int index = luaL_checkinteger(L, 1); + + if (index < 0 || index >= ajpoly::num_things) + return 0; + + const ajpoly::thing_c * TH = ajpoly::Thing(index); + + lua_newtable(L); + + lua_pushinteger(L, TH->x); + lua_setfield(L, -2, "x"); + + lua_pushinteger(L, TH->y); + lua_setfield(L, -2, "y"); + + lua_pushinteger(L, calc_thing_z(TH->x, TH->y)); + lua_setfield(L, -2, "z"); + + lua_pushinteger(L, TH->angle); + lua_setfield(L, -2, "angle"); + + lua_pushinteger(L, TH->type); + lua_setfield(L, -2, "type"); + + lua_pushinteger(L, TH->options); + lua_setfield(L, -2, "options"); + + lua_pushinteger(L, TH->special); + lua_setfield(L, -2, "special"); + + lua_pushinteger(L, TH->index); + lua_setfield(L, -2, "tid"); + + lua_pushinteger(L, TH->args[0]); + lua_setfield(L, -2, "arg1"); + + lua_pushinteger(L, TH->args[1]); + lua_setfield(L, -2, "arg2"); + + lua_pushinteger(L, TH->args[2]); + lua_setfield(L, -2, "arg3"); + + lua_pushinteger(L, TH->args[3]); + lua_setfield(L, -2, "arg4"); + + lua_pushinteger(L, TH->args[4]); + lua_setfield(L, -2, "arg5"); + + return 1; +} + int wadfab_get_sector(lua_State *L) { diff --git a/gui/m_lua.cc b/gui/m_lua.cc index fe19b647b4..f150065f65 100644 --- a/gui/m_lua.cc +++ b/gui/m_lua.cc @@ -873,6 +873,7 @@ extern int wadfab_get_line(lua_State *L); extern int wadfab_get_line_hexen(lua_State *L); extern int wadfab_get_3d_floor(lua_State *L); extern int wadfab_get_thing(lua_State *L); +extern int wadfab_get_thing_hexen(lua_State *L); extern int Q1_add_mapmodel(lua_State *L); extern int Q1_add_tex_wad(lua_State *L); @@ -973,6 +974,7 @@ static const luaL_Reg gui_script_funcs[] = { "wadfab_get_line_hexen", wadfab_get_line_hexen }, { "wadfab_get_3d_floor", wadfab_get_3d_floor }, { "wadfab_get_thing", wadfab_get_thing }, + { "wadfab_get_thing_hexen", wadfab_get_thing_hexen }, // Quake functions { "q1_add_mapmodel", Q1_add_mapmodel }, diff --git a/scripts/prefab.lua b/scripts/prefab.lua index 4190693983..409b0d73e6 100644 --- a/scripts/prefab.lua +++ b/scripts/prefab.lua @@ -1717,7 +1717,11 @@ function Fab_load_wad(def) gui.wadfab_load(filename, def.map or "*") for thing_idx = 0,9999 do - local E = gui.wadfab_get_thing(thing_idx) +-- if OB_CONFIG.game == "hexen" then +-- local E = gui.wadfab_get_thing_hexen(thing_idx) +-- else + local E = gui.wadfab_get_thing(thing_idx) +-- end -- nil result marks the end if not E then break; end @@ -1754,7 +1758,11 @@ function Fab_load_wad(def) end for line_idx = 0,9999 do - local L = gui.wadfab_get_line(line_idx) +-- if OB_CONFIG.game == "hexen" then +-- local L = gui.wadfab_get_line_hexen(line_idx) +-- else + local L = gui.wadfab_get_line(line_idx) +-- end -- nil result marks the end if not L then break; end @@ -2085,7 +2093,7 @@ function Fab_replacements(fab) return fab.fields[k] end - + local function get_entity_id(name) -- allow specifying a raw ID number @@ -2178,6 +2186,10 @@ function Fab_replacements(fab) if C.special and not C.x then C.special = check("sector", C.special) end if C.tag then C.tag = check_tag(C.tag) end + + if OB_CONFIG.game == "hexen" then + if C.arg1 then C.arg1 = C.tag end + end if C.u1 then C.u1 = check("offset", C.u1) end if C.v1 then C.v1 = check("offset", C.v1) end From 67405225127fde526400d9f9e3ee5ab2eaf80320 Mon Sep 17 00:00:00 2001 From: Dasho Danger Date: Fri, 19 Mar 2021 15:48:42 +0430 Subject: [PATCH 03/20] More Hexen prefab work --- gui/dm_prefab.cc | 13 +++++-------- scripts/prefab.lua | 28 ++++++++++++++++------------ 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/gui/dm_prefab.cc b/gui/dm_prefab.cc index 99ee9e8546..29d4d65898 100644 --- a/gui/dm_prefab.cc +++ b/gui/dm_prefab.cc @@ -209,6 +209,9 @@ int wadfab_get_thing_hexen(lua_State *L) lua_newtable(L); + lua_pushinteger(L, TH->type); + lua_setfield(L, -2, "id"); + lua_pushinteger(L, TH->x); lua_setfield(L, -2, "x"); @@ -221,18 +224,12 @@ int wadfab_get_thing_hexen(lua_State *L) lua_pushinteger(L, TH->angle); lua_setfield(L, -2, "angle"); - lua_pushinteger(L, TH->type); - lua_setfield(L, -2, "type"); - lua_pushinteger(L, TH->options); - lua_setfield(L, -2, "options"); + lua_setfield(L, -2, "flags"); lua_pushinteger(L, TH->special); lua_setfield(L, -2, "special"); - - lua_pushinteger(L, TH->index); - lua_setfield(L, -2, "tid"); - + lua_pushinteger(L, TH->args[0]); lua_setfield(L, -2, "arg1"); diff --git a/scripts/prefab.lua b/scripts/prefab.lua index 409b0d73e6..0f45a4c968 100644 --- a/scripts/prefab.lua +++ b/scripts/prefab.lua @@ -1301,7 +1301,7 @@ function Fab_load_wad(def) else -- keep these flags: block-all, block-mon, secret, no-draw, -- always-draw, block-sound, pass-thru - flags = bit.band(flags, 0xFFF) + flags = bit.band(flags, 0xFFFF) if flags ~= 0 then C2.flags = flags @@ -1550,7 +1550,7 @@ function Fab_load_wad(def) local function handle_entity(fab, E) local spot_info = WADFAB_ENTITIES[E.id] - + if not spot_info then table.insert(fab.entities, E) return @@ -1716,12 +1716,14 @@ function Fab_load_wad(def) -- [ if map is not specified, use "*" to load the first one ] gui.wadfab_load(filename, def.map or "*") + local E + for thing_idx = 0,9999 do --- if OB_CONFIG.game == "hexen" then --- local E = gui.wadfab_get_thing_hexen(thing_idx) --- else - local E = gui.wadfab_get_thing(thing_idx) --- end + if OB_CONFIG.game == "hexen" then + E = gui.wadfab_get_thing_hexen(thing_idx) + else + E = gui.wadfab_get_thing(thing_idx) + end -- nil result marks the end if not E then break; end @@ -1756,13 +1758,15 @@ function Fab_load_wad(def) end ::continue:: end + + local L for line_idx = 0,9999 do --- if OB_CONFIG.game == "hexen" then --- local L = gui.wadfab_get_line_hexen(line_idx) --- else - local L = gui.wadfab_get_line(line_idx) --- end + if OB_CONFIG.game == "hexen" then + L = gui.wadfab_get_line_hexen(line_idx) + else + L = gui.wadfab_get_line(line_idx) + end -- nil result marks the end if not L then break; end From 4d4e738ba599f530b290f4efc5fe4fc28fc16ddb Mon Sep 17 00:00:00 2001 From: Dasho Danger Date: Fri, 19 Mar 2021 16:46:53 +0430 Subject: [PATCH 04/20] More Hexen prefab work --- scripts/prefab.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/prefab.lua b/scripts/prefab.lua index 0f45a4c968..75edc35b61 100644 --- a/scripts/prefab.lua +++ b/scripts/prefab.lua @@ -1301,14 +1301,14 @@ function Fab_load_wad(def) else -- keep these flags: block-all, block-mon, secret, no-draw, -- always-draw, block-sound, pass-thru - flags = bit.band(flags, 0xFFFF) - if flags ~= 0 then C2.flags = flags -- this makes sure the flags get applied if not C2.special then C2.special = 0 end end + + C.act = bit.band(flags, 0x1C00) >> 10 upper_unpeg = (bit.band(flags, MLF_UpperUnpegged) ~= 0) lower_unpeg = (bit.band(flags, MLF_LowerUnpegged) ~= 0) From 59610468f951a2784d63068ab659d98f28b0b1ca Mon Sep 17 00:00:00 2001 From: Dasho Danger Date: Sat, 20 Mar 2021 09:31:14 +0430 Subject: [PATCH 05/20] Fix for Hexen linedef arg1 not matching the target sector tag (only working for specials 10-12 at the moment) --- scripts/prefab.lua | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/scripts/prefab.lua b/scripts/prefab.lua index 75edc35b61..8a580e2081 100644 --- a/scripts/prefab.lua +++ b/scripts/prefab.lua @@ -2184,15 +2184,21 @@ function Fab_replacements(fab) build_entity_remap_table() + current_tag = 0 -- Used to help Hexen arg1 match with appropriate sector tag when needed + for _,B in pairs(fab.brushes) do + print("CURRENT TAG: " .. current_tag) for _,C in pairs(B) do if C.special and C.x then C.special = check("line", C.special) end if C.special and not C.x then C.special = check("sector", C.special) end - if C.tag then C.tag = check_tag(C.tag) end + if C.tag then + C.tag = check_tag(C.tag) + current_tag = C.tag + end if OB_CONFIG.game == "hexen" then - if C.arg1 then C.arg1 = C.tag end + if C.x and C.special and (C.special >= 10 and C.special <= 12) then C.arg1 = current_tag end -- Flesh out which special ranges need to have arg1 match a sector tag end if C.u1 then C.u1 = check("offset", C.u1) end From fb967ae323d7392a6476cafdca4f25635fb0257c Mon Sep 17 00:00:00 2001 From: Dasho Danger Date: Sat, 20 Mar 2021 12:43:46 +0430 Subject: [PATCH 06/20] Added SPAC activation lines for Hexen UDMF format --- gui/g_doom.cc | 42 ++++++++++++++++++++++-------------------- scripts/prefab.lua | 1 - 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/gui/g_doom.cc b/gui/g_doom.cc index ee943df992..8d0fb72c90 100644 --- a/gui/g_doom.cc +++ b/gui/g_doom.cc @@ -532,10 +532,7 @@ void DM_AddLinedef(int vert1, int vert2, int side1, int side2, else { textmap_lump->Printf("\nlinedef\n{\n"); - if (type == 121) - { - textmap_lump->Printf("\tid = %d;\n", args[0]); - } + textmap_lump->Printf("\tid = %d;\n", args[0]); textmap_lump->Printf("\tv1 = %d;\n", vert1); textmap_lump->Printf("\tv2 = %d;\n", vert2); textmap_lump->Printf("\tsidefront = %d;\n", side1 < 0 ? -1 : side1); @@ -548,22 +545,12 @@ void DM_AddLinedef(int vert1, int vert2, int side1, int side2, else { textmap_lump->Printf("\tspecial = %d;\n", type); - if (args) - { - textmap_lump->Printf("\targ0 = %d;\n", args[0]); - } - else - { - textmap_lump->Printf("\targ0 = 0;\n"); - } - } - if (args) - { - textmap_lump->Printf("\targ1 = %d;\n", args[1]); - textmap_lump->Printf("\targ2 = %d;\n", args[2]); - textmap_lump->Printf("\targ3 = %d;\n", args[3]); - textmap_lump->Printf("\targ4 = %d;\n", args[4]); + textmap_lump->Printf("\targ0 = %d;\n", args[0]); } + textmap_lump->Printf("\targ1 = %d;\n", args[1]); + textmap_lump->Printf("\targ2 = %d;\n", args[2]); + textmap_lump->Printf("\targ3 = %d;\n", args[3]); + textmap_lump->Printf("\targ4 = %d;\n", args[4]); std::bitset<16> udmf_flags(flags); if (udmf_flags.test(0)) textmap_lump->Printf("\tblocking = true;\n"); @@ -585,8 +572,23 @@ void DM_AddLinedef(int vert1, int vert2, int side1, int side2, textmap_lump->Printf("\tmapped = true;\n"); if (udmf_flags.test(9)) textmap_lump->Printf("\trepeatspecial = true;\n"); + int spac = (flags & 0x1C00) >> 10; + if (type > 0) + { + if ((spac == 0x0000) || (spac == 0x0200)) + textmap_lump->Printf("\tplayercross = true;\n"); + if ((spac == 0x0400) || (spac == 0x0600)) + textmap_lump->Printf("\tplayeruse = true;\n"); + if ((spac == 0x0800) || (spac == 0x0A00)) + textmap_lump->Printf("\tmonstercross = true;\n"); + if ((spac == 0x0C00) || (spac == 0x0E00)) + textmap_lump->Printf("\timpact = true;\n"); + if ((spac == 0x1000) || (spac == 0x1200)) + textmap_lump->Printf("\tplayerpush = true;\n"); + if ((spac == 0x1400) || (spac == 0x1600)) + textmap_lump->Printf("\tmissilecross = true;\n"); + } textmap_lump->Printf("}\n"); -// LogPrintf("ACTIVATION: %d\n", ((flags & 0x1C00) >> 10)); To determine SPAC activation if necessary. Placeholder for now. udmf_linedefs += 1; } } diff --git a/scripts/prefab.lua b/scripts/prefab.lua index 8a580e2081..b5ad246d96 100644 --- a/scripts/prefab.lua +++ b/scripts/prefab.lua @@ -2187,7 +2187,6 @@ function Fab_replacements(fab) current_tag = 0 -- Used to help Hexen arg1 match with appropriate sector tag when needed for _,B in pairs(fab.brushes) do - print("CURRENT TAG: " .. current_tag) for _,C in pairs(B) do if C.special and C.x then C.special = check("line", C.special) end if C.special and not C.x then C.special = check("sector", C.special) end From fa896a27974ace4fd7bbb652c505d85665389ba8 Mon Sep 17 00:00:00 2001 From: Dasho Danger Date: Sat, 20 Mar 2021 13:16:05 +0430 Subject: [PATCH 07/20] Tweak SPAC activation values --- gui/g_doom.cc | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/gui/g_doom.cc b/gui/g_doom.cc index 8d0fb72c90..0adcfce53d 100644 --- a/gui/g_doom.cc +++ b/gui/g_doom.cc @@ -532,7 +532,10 @@ void DM_AddLinedef(int vert1, int vert2, int side1, int side2, else { textmap_lump->Printf("\nlinedef\n{\n"); - textmap_lump->Printf("\tid = %d;\n", args[0]); + if (type == 121) + { + textmap_lump->Printf("\tid = %d;\n", args[0]); + } textmap_lump->Printf("\tv1 = %d;\n", vert1); textmap_lump->Printf("\tv2 = %d;\n", vert2); textmap_lump->Printf("\tsidefront = %d;\n", side1 < 0 ? -1 : side1); @@ -575,17 +578,17 @@ void DM_AddLinedef(int vert1, int vert2, int side1, int side2, int spac = (flags & 0x1C00) >> 10; if (type > 0) { - if ((spac == 0x0000) || (spac == 0x0200)) + if (spac == 0) textmap_lump->Printf("\tplayercross = true;\n"); - if ((spac == 0x0400) || (spac == 0x0600)) + if (spac == 1) textmap_lump->Printf("\tplayeruse = true;\n"); - if ((spac == 0x0800) || (spac == 0x0A00)) + if (spac == 2) textmap_lump->Printf("\tmonstercross = true;\n"); - if ((spac == 0x0C00) || (spac == 0x0E00)) + if (spac == 3) textmap_lump->Printf("\timpact = true;\n"); - if ((spac == 0x1000) || (spac == 0x1200)) + if (spac == 4) textmap_lump->Printf("\tplayerpush = true;\n"); - if ((spac == 0x1400) || (spac == 0x1600)) + if (spac == 5) textmap_lump->Printf("\tmissilecross = true;\n"); } textmap_lump->Printf("}\n"); From 218ac4924d67759b1b44fb983c6b33cd4c2c3d38 Mon Sep 17 00:00:00 2001 From: Dasho Danger Date: Sat, 20 Mar 2021 13:21:55 +0430 Subject: [PATCH 08/20] Matched version number with stable --- gui/main.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gui/main.h b/gui/main.h index d6bbc5efc7..5d75c4dda6 100644 --- a/gui/main.h +++ b/gui/main.h @@ -23,8 +23,8 @@ #define OBSIDIAN_TITLE "OBSIDIAN Level Maker" -#define OBSIDIAN_VERSION "Beta 9" -#define OBSIDIAN_HEX_VER 0x009 +#define OBSIDIAN_VERSION "Beta 10" +#define OBSIDIAN_HEX_VER 0x00A #define OBSIDIAN_WEBSITE "https://github.com/GTD-Carthage/Oblige" From 671921c0a72f292ffcc27f4bab844e067bfdaf70 Mon Sep 17 00:00:00 2001 From: Dasho Danger Date: Sat, 20 Mar 2021 16:41:57 +0430 Subject: [PATCH 09/20] Implemented basic reject builder --- Makefile | 1 + gui/g_doom.cc | 6 +- zdbsp_src/Dasho-Compilation-Tips.txt | 6 - zdbsp_src/Unused/rejectbuilder.cc | 215 ++++ zdbsp_src/{ => Unused}/view.cc | 0 zdbsp_src/Unused/vis.cc | 586 +++++++++++ zdbsp_src/Unused/visflow.cc | 1373 ++++++++++++++++++++++++++ zdbsp_src/{ => Unused}/zipbin.bat | 0 zdbsp_src/processor.cc | 21 +- zdbsp_src/processor_udmf-2.cc | 606 ------------ zdbsp_src/rejectbuilder.cc | 278 ++++++ zdbsp_src/rejectbuilder.h | 40 + 12 files changed, 2509 insertions(+), 623 deletions(-) delete mode 100644 zdbsp_src/Dasho-Compilation-Tips.txt create mode 100644 zdbsp_src/Unused/rejectbuilder.cc rename zdbsp_src/{ => Unused}/view.cc (100%) create mode 100644 zdbsp_src/Unused/vis.cc create mode 100644 zdbsp_src/Unused/visflow.cc rename zdbsp_src/{ => Unused}/zipbin.bat (100%) delete mode 100644 zdbsp_src/processor_udmf-2.cc create mode 100644 zdbsp_src/rejectbuilder.cc create mode 100644 zdbsp_src/rejectbuilder.h diff --git a/Makefile b/Makefile index 4e22612540..b79e4164f7 100644 --- a/Makefile +++ b/Makefile @@ -173,6 +173,7 @@ ZDBSP_OBJS= \ $(OBJ_DIR)/zdbsp/sc_man.o \ $(OBJ_DIR)/zdbsp/zdwad.o \ $(OBJ_DIR)/zdbsp/nodebuild.o \ + $(OBJ_DIR)/zdbsp/rejectbuilder.o \ $(OBJ_DIR)/zdbsp/nodebuild_events.o \ $(OBJ_DIR)/zdbsp/nodebuild_extract.o \ $(OBJ_DIR)/zdbsp/nodebuild_gl.o \ diff --git a/gui/g_doom.cc b/gui/g_doom.cc index 0adcfce53d..502a7cc3c3 100644 --- a/gui/g_doom.cc +++ b/gui/g_doom.cc @@ -853,7 +853,7 @@ static bool DM_BuildNodes(const char *filename, const char *out_name) { options.build_nodes = true; options.build_gl_nodes = false; - options.reject_mode = ERM_CreateZeroes; + options.reject_mode = ERM_Rebuild; options.check_polyobjs = false; options.compress_nodes = false; options.compress_gl_nodes = false; @@ -863,7 +863,7 @@ static bool DM_BuildNodes(const char *filename, const char *out_name) { options.build_nodes = true; options.build_gl_nodes = false; - options.reject_mode = ERM_CreateZeroes; + options.reject_mode = ERM_Rebuild; options.check_polyobjs = false; options.compress_nodes = true; options.compress_gl_nodes = true; @@ -888,7 +888,7 @@ static bool DM_BuildNodes(const char *filename, const char *out_name) zdbsp_options options; options.build_nodes = true; options.build_gl_nodes = false; - options.reject_mode = ERM_DontTouch; + options.reject_mode = ERM_Rebuild; options.check_polyobjs = true; options.compress_nodes = true; options.compress_gl_nodes = true; diff --git a/zdbsp_src/Dasho-Compilation-Tips.txt b/zdbsp_src/Dasho-Compilation-Tips.txt deleted file mode 100644 index fbe3cb5e23..0000000000 --- a/zdbsp_src/Dasho-Compilation-Tips.txt +++ /dev/null @@ -1,6 +0,0 @@ -To create the necessary files to cross-compile ZDBSP for Obsidian on Windows: -cd mingw -cmake .. -DCMAKE_TOOLCHAIN_FILE=../Toolchain-mingw64.cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_RC_COMPILER=/usr/bin/x86_64-w64-mingw32-windres -DCMAKE_EXE_LINKER_FLAGS_RELEASE="-static-libgcc -static-libstdc++" -make - -Replace Toolchain-mingw64.cmake with Toolchain-mingw32.cmake and x86_64-w64-mingw32-windres with i686-w64-mingw32-windres if building for 32-bit diff --git a/zdbsp_src/Unused/rejectbuilder.cc b/zdbsp_src/Unused/rejectbuilder.cc new file mode 100644 index 0000000000..7767c62c46 --- /dev/null +++ b/zdbsp_src/Unused/rejectbuilder.cc @@ -0,0 +1,215 @@ +// The old code used the same algorithm as DoomBSP: +// +// Represent each sector by its bounding box. Then for each pair of +// sectors, see if any chains of one-sided lines can walk from one +// side of the convex hull for that pair to the other side. +// +// It works, but it's far from being perfect. It's quite easy for +// this algorithm to consider two sectors as being visible from +// each other when they are really not. But it won't erroneously +// flag two sectors as obstructed when they're really not, and that's +// the only thing that really matters when building a REJECT lump. +// +// Because that was next to useless, I scrapped that code and adapted +// Quake's vis utility to work in a 2D world. Since this is basically vis, +// it depends on GL nodes being present to function. As the usefulness +// of a REJECT lump is debatable, I have chosen to not compile this module +// in with ZDBSP. Save yourself some space and run ZDBSP with the -r option. + +#include +#include + +#include "zdbsp.h" +#include "nodebuild.h" +#include "rejectbuilder.h" +#include "templates.h" + +bool MergeVis=1; + +FRejectBuilder::FRejectBuilder (FLevel &level) + : Level (level), testlevel (2), totalvis (0) +{ + LoadPortals (); + + if (MergeVis) + { + MergeLeaves (); + MergeLeafPortals (); + } + + CountActivePortals (); + CalcVis (); + + BuildReject (); +} + +FRejectBuilder::~FRejectBuilder () +{ +} + +BYTE *FRejectBuilder::GetReject () +{ + WORD *sectormap; + int i, j; + + int rejectSize = (Level.NumSectors()*Level.NumSectors() + 7) / 8; + BYTE *reject = new BYTE[rejectSize]; + memset (reject, 0xff, rejectSize); + + int pvs_size = (Level.NumGLSubsectors * Level.NumGLSubsectors) + 7 / 8; + Level.GLPVS = new BYTE[pvs_size]; + Level.GLPVSSize = pvs_size; + memset (Level.GLPVS, 0, pvs_size); + + sectormap = new WORD[Level.NumGLSubsectors]; + for (i = 0; i < Level.NumGLSubsectors; ++i) + { + const MapSegGLEx *seg = &Level.GLSegs[Level.GLSubsectors[i].firstline]; + sectormap[i] = Level.Sides[Level.Lines[seg->linedef].sidenum[seg->side]].sector; + } + + for (i = 0; i < Level.NumGLSubsectors; ++i) + { + int rowpvs = i*Level.NumGLSubsectors; + int rowrej = sectormap[i]*Level.NumSectors(); + BYTE *bytes = visBytes + i*leafbytes; + for (j = 0; j < Level.NumGLSubsectors; ++j) + { + if (bytes[j>>3] & (1<<(j&7))) + { + int mark = rowpvs + j; + Level.GLPVS[mark>>3] |= 1<<(mark&7); + + mark = rowrej + sectormap[j]; + reject[mark>>3] &= ~(1<<(mark&7)); + } + } + } + + return reject; +} + +void FRejectBuilder::BuildReject () +{ +} + +inline const WideVertex *FRejectBuilder::GetVertex (WORD vertnum) +{ + return &Level.Vertices[vertnum]; +} + +FRejectBuilder::FLeaf::FLeaf () + : numportals (0), merged (-1), portals (NULL) +{ +} + +FRejectBuilder::FLeaf::~FLeaf () +{ + if (portals != NULL) + { + delete[] portals; + } +} + +int FRejectBuilder::PointOnSide (const FPoint &point, const FLine &line) +{ + return FNodeBuilder::PointOnSide (point.x, point.y, line.x, line.y, line.dx, line.dy); +} + +void FRejectBuilder::LoadPortals () +{ + WORD *segleaf; + int i, j, k, max; + VPortal *p; + FLeaf *l; + FWinding *w; + + portalclusters = Level.NumGLSubsectors; + + for (numportals = 0, i = 0; i < Level.NumGLSegs; ++i) + { + if (Level.GLSegs[i].partner != DWORD_MAX) + { + ++numportals; + } + } + + // these counts should take advantage of 64 bit systems automatically + leafbytes = ((portalclusters+63)&~63)>>3; + leaflongs = leafbytes/sizeof(long); + + portalbytes = ((numportals+63)&~63)>>3; + portallongs = portalbytes/sizeof(long); + + portals = new VPortal[numportals]; + memset (portals, 0, numportals*sizeof(VPortal)); + + leafs = new FLeaf[portalclusters]; + + numVisBytes = portalclusters*leafbytes; + visBytes = new BYTE[numVisBytes]; + + segleaf = new WORD[Level.NumGLSegs]; + for (i = 0; i < Level.NumGLSubsectors; ++i) + { + j = Level.GLSubsectors[i].firstline; + max = j + Level.GLSubsectors[i].numlines; + + for (; j < max; ++j) + { + segleaf[j] = i; + } + } + + p = portals; + l = leafs; + for (i = 0; i < Level.NumGLSubsectors; ++i, ++l) + { + j = Level.GLSubsectors[i].firstline; + max = j + Level.GLSubsectors[i].numlines; + + // Count portals in this leaf + for (; j < max; ++j) + { + if (Level.GLSegs[j].partner != DWORD_MAX) + { + ++l->numportals; + } + } + + if (l->numportals == 0) + { + continue; + } + + l->portals = new VPortal *[l->numportals]; + + for (k = 0, j = Level.GLSubsectors[i].firstline; j < max; ++j) + { + const MapSegGLEx *seg = &Level.GLSegs[j]; + + if (seg->partner == DWORD_MAX) + { + continue; + } + + // create portal from seg + l->portals[k++] = p; + + w = &p->winding; + w->points[0] = GetVertex (seg->v1); + w->points[1] = GetVertex (seg->v2); + + p->hint = seg->linedef != NO_INDEX; + p->line.x = w->points[1].x; + p->line.y = w->points[1].y; + p->line.dx = w->points[0].x - p->line.x; + p->line.dy = w->points[0].y - p->line.y; + p->leaf = segleaf[seg->partner]; + + p++; + } + } + + delete[] segleaf; +} diff --git a/zdbsp_src/view.cc b/zdbsp_src/Unused/view.cc similarity index 100% rename from zdbsp_src/view.cc rename to zdbsp_src/Unused/view.cc diff --git a/zdbsp_src/Unused/vis.cc b/zdbsp_src/Unused/vis.cc new file mode 100644 index 0000000000..5ae726a18f --- /dev/null +++ b/zdbsp_src/Unused/vis.cc @@ -0,0 +1,586 @@ +// An adaptation of the Quake vis utility. + +#include +#include + +#include "zdbsp.h" +#include "nodebuild.h" +#include "rejectbuilder.h" +#include "templates.h" +#include + +bool FastVis=0; +bool NoPassageVis=1; +bool NoSort; + +//============================================================================= + +/* +============= +GetThreadWork + +============= +*/ +int FRejectBuilder::GetThreadWork () +{ + int r; + int f; + + if (dispatch == workcount) + { + return -1; + } + + if (pacifier) + { + if (dispatch >= workcount-1) + { + fprintf (stderr, "100%%\n"); + } + else if (oldcount < 0 || dispatch - oldcount > 200) + { + oldcount = dispatch; + f = 100*dispatch / workcount; + if (f != oldf) + { + oldf = f; + fprintf (stderr, "% 3d%%\b\b\b\b", f); + } + } + } + + r = dispatch++; + + return r; +} +void FRejectBuilder::RunThreadsOnIndividual (int workcnt, bool showpacifier, void(FRejectBuilder::*func)(int)) +{ + int work; + + pacifier = showpacifier; + workcount = workcnt; + dispatch = 0; + oldf = -1; + oldcount = -1; + + while (-1 != (work = GetThreadWork ())) + { + (this->*func) (work); + } +} + +//============================================================================= + +/* +============= +SortPortals + +Sorts the portals from the least complex, so the later ones can reuse +the earlier information. +============= +*/ +int FRejectBuilder::PComp (const void *a, const void *b) +{ + return (*(VPortal **)a)->nummightsee - (*(VPortal **)b)->nummightsee; +} +void FRejectBuilder::SortPortals () +{ + for (int i = 0; i < numportals; i++) + { + sorted_portals[i] = &portals[i]; + } + + if (!NoSort) + { + qsort (sorted_portals, numportals, sizeof(sorted_portals[0]), PComp); + } +} + + +/* +============== +LeafVectorFromPortalVector +============== +*/ +int FRejectBuilder::LeafVectorFromPortalVector (BYTE *portalbits, BYTE *leafbits) +{ + int i, j, leafnum; + VPortal *p; + int c_leafs; + + + for (i = 0; i < numportals; i++) + { + if (portalbits[i>>3] & (1<<(i&7)) ) + { + p = portals+i; + leafbits[p->leaf>>3] |= (1<<(p->leaf&7)); + //printf ("pleaf: from %d to %d\n", i, p->leaf); + } + } + + for (j = 0; j < portalclusters; j++) + { + leafnum = j; + while (leafs[leafnum].merged >= 0) + { + leafnum = leafs[leafnum].merged; + } + //if the merged leaf is visible then the original leaf is visible + if (leafbits[leafnum>>3] & (1<<(leafnum&7))) + { + leafbits[j>>3] |= (1<<(j&7)); + } + } + + c_leafs = CountBits (leafbits, portalclusters); + + return c_leafs; +} + + +/* +=============== +ClusterMerge + +Merges the portal visibility for a leaf +=============== +*/ +void FRejectBuilder::ClusterMerge (int leafnum) +{ + FLeaf *leaf; + BYTE portalvector[MAX_PORTALS/8]; + BYTE uncompressed[MAX_MAP_LEAFS/8]; + int i, j; + int numvis, mergedleafnum; + VPortal *p; + int pnum; + + // OR together all the portalvis bits + + mergedleafnum = leafnum; + while (leafs[mergedleafnum].merged >= 0) + { + mergedleafnum = leafs[mergedleafnum].merged; + } + + memset (portalvector, 0, portalbytes); + leaf = &leafs[mergedleafnum]; + for (i = 0; i < leaf->numportals; i++) + { + p = leaf->portals[i]; + if (p->removed) + continue; + try + { + if (p->status != STAT_Done) + { + throw std::runtime_error("portal not done"); + } + } + catch (std::runtime_error &msg) + { + printf ("%s\n", msg.what()); + } + for (j = 0; j < portallongs; j++) + { + ((long *)portalvector)[j] |= ((long *)p->portalvis)[j]; + } + pnum = p - portals; + portalvector[pnum>>3] |= 1<<(pnum&7); + } + + memset (uncompressed, 0, leafbytes); + + // convert portal bits to leaf bits + uncompressed[mergedleafnum>>3] |= (1<<(mergedleafnum&7)); + numvis = LeafVectorFromPortalVector (portalvector, uncompressed); + + totalvis += numvis; + + //printf ("cluster %4i : %4i visible\n", leafnum, numvis); + + memcpy (visBytes + leafnum*leafbytes, uncompressed, leafbytes); +} + + +/* +================== +CalcPortalVis +================== +*/ +void FRejectBuilder::CalcPortalVis () +{ +#ifdef MREDEBUG + _printf("%6d portals out of %d", 0, numportals); + //get rid of the counter + RunThreadsOnIndividual (numportals, false, &FRejectBuilder::PortalFlow); +#else + RunThreadsOnIndividual (numportals, true, &FRejectBuilder::PortalFlow); +#endif + +} + +/* +================== +CalcPassagePortalVis +================== +*/ + +void FRejectBuilder::CalcPassagePortalVis () +{ + PassageMemory(); +#ifdef MREDEBUG + _printf("%6d portals out of %d", 0, numportals); + RunThreadsOnIndividual (numportals, false, &FRejectBuilder::CreatePassages); + _printf("\n"); + _printf("%6d portals out of %d", 0, numportals); + RunThreadsOnIndividual (numportals, false, &FRejectBuilder::PassagePortalFlow); + _printf("\n"); +#else + RunThreadsOnIndividual (numportals, true, &FRejectBuilder::CreatePassages); + printf (" Vis 3: "); + RunThreadsOnIndividual (numportals, true, &FRejectBuilder::PassagePortalFlow); +#endif +} + +/* +================== +CalcFastVis +================== +*/ +void FRejectBuilder::CalcFastVis () +{ + // fastvis just uses mightsee for a very loose bound + for (int i = 0; i < numportals; i++) + { + portals[i].portalvis = portals[i].portalflood; + portals[i].status = STAT_Done; + } +} + +/* +================== +CalcVis +================== +*/ +void FRejectBuilder::CalcVis () +{ + printf (" Vis 1: "); + RunThreadsOnIndividual (numportals, true, &FRejectBuilder::BasePortalVis); + +// RunThreadsOnIndividual (numportals, true, BetterPortalVis); + + SortPortals (); + + printf (" Vis 2: "); + if (FastVis) + CalcFastVis(); + else if (NoPassageVis) + CalcPortalVis (); + else + CalcPassagePortalVis(); + // + // assemble the leaf vis lists by oring and compressing the portal lists + // + printf ("creating leaf vis...\n"); + for (int i = 0; i < portalclusters; i++) + { + ClusterMerge (i); + } + + printf ("Average clusters visible: %i\n", totalvis / portalclusters); +} + +/* +============= +Winding_PlanesConcave +============= +*/ +bool FRejectBuilder::Winding_PlanesConcave (const FWinding *w1, const FWinding *w2, + const FLine &line1, const FLine &line2) +{ + // check if one of the points of winding 1 is at the front of the line of winding 2 + if (PointOnSide (w1->points[0], line2) < 0 || + PointOnSide (w1->points[1], line2) < 0) + { + return true; + } + + // check if one of the points of winding 2 is at the front of the line of winding 1 + if (PointOnSide (w2->points[0], line1) < 0 || + PointOnSide (w2->points[1], line1) < 0) + { + return true; + } + + return false; +} + +/* +============ +TryMergeLeaves +============ +*/ +bool FRejectBuilder::TryMergeLeaves (int l1num, int l2num) +{ + int i, j, numportals; + FLeaf *l1, *l2; + VPortal *p1, *p2; + VPortal *portals[MAX_PORTALS_ON_LEAF]; + + l1 = &leafs[l1num]; + for (i = 0; i < l1->numportals; i++) + { + p1 = l1->portals[i]; + if (p1->leaf == l2num) continue; + l2 = &leafs[l2num]; + for (j = 0; j < l2->numportals; j++) + { + p2 = l2->portals[j]; + if (p2->leaf == l1num) continue; + // + if (Winding_PlanesConcave (&p1->winding, &p2->winding, p1->line, p2->line)) + return false; + } + } + l1 = &leafs[l1num]; + l2 = &leafs[l2num]; + numportals = 0; + //the leaves can be merged now + for (i = 0; i < l1->numportals; i++) + { + p1 = l1->portals[i]; + if (p1->leaf == l2num) + { + p1->removed = true; + continue; + } + portals[numportals++] = p1; + } + for (j = 0; j < l2->numportals; j++) + { + p2 = l2->portals[j]; + if (p2->leaf == l1num) + { + p2->removed = true; + continue; + } + portals[numportals++] = p2; + } + delete[] l1->portals; + l1->portals = NULL; + l1->numportals = 0; + delete[] l2->portals; + l2->portals = new VPortal *[numportals]; + for (i = 0; i < numportals; i++) + { + l2->portals[i] = portals[i]; + } + l2->numportals = numportals; + l1->merged = l2num; + return true; +} + +/* +============ +UpdatePortals +============ +*/ +void FRejectBuilder::UpdatePortals () +{ + int i; + VPortal *p; + + for (i = 0; i < numportals; i++) + { + p = &portals[i]; + if (!p->removed) + { + while (leafs[p->leaf].merged >= 0) + { + p->leaf = leafs[p->leaf].merged; + } + } + } +} + +/* +============ +MergeLeaves + +try to merge leaves but don't merge through hint splitters +============ +*/ +void FRejectBuilder::MergeLeaves () +{ + int i, j, nummerges, totalnummerges; + FLeaf *leaf; + VPortal *p; + + totalnummerges = 0; + do + { + printf ("."); + nummerges = 0; + for (i = 0; i < portalclusters; i++) + { + leaf = &leafs[i]; + //if this leaf is merged already + if (leaf->merged >= 0) + continue; + // + for (j = 0; j < leaf->numportals; j++) + { + p = leaf->portals[j]; + // + if (p->removed) + continue; + //never merge through hint portals + if (p->hint) + continue; + if (TryMergeLeaves(i, p->leaf)) + { + UpdatePortals(); + nummerges++; + break; + } + } + } + totalnummerges += nummerges; + } while (nummerges); + printf("\r%6d leaves merged\n", totalnummerges); +} + +/* +============ +TryMergeWinding +============ +*/ + +FRejectBuilder::FWinding *FRejectBuilder::TryMergeWinding (FWinding *f1, FWinding *f2, const FLine &line) +{ + static FWinding result; + int i, j; + + // + // find a common point + // + for (i = 0; i < 2; ++i) + { + for (j = 0; j < 2; ++j) + { + if (f1->points[i].x == f2->points[j].x && + f1->points[i].y == f2->points[j].y) + { + goto found; + } + } + } + + // no shared point + return NULL; + +found: + // + // if the lines are colinear, the point can be removed + // + if (PointOnSide (f2->points[0], line) != 0 || + PointOnSide (f2->points[1], line) != 0) + { // not colinear + return NULL; + } + + // + // build the new segment + // + if (i == 0) + { + result.points[0] = f2->points[!j]; + result.points[1] = f1->points[1]; + } + else + { + result.points[0] = f1->points[0]; + result.points[1] = f2->points[!j]; + } + return &result; +} + +/* +============ +MergeLeafPortals +============ +*/ +void FRejectBuilder::MergeLeafPortals () +{ + int i, j, k, nummerges, hintsmerged; + FLeaf *leaf; + VPortal *p1, *p2; + FWinding *w; + + nummerges = 0; + hintsmerged = 0; + for (i = 0; i < portalclusters; i++) + { + leaf = &leafs[i]; + if (leaf->merged >= 0) continue; + for (j = 0; j < leaf->numportals; j++) + { + p1 = leaf->portals[j]; + if (p1->removed) + continue; + for (k = j+1; k < leaf->numportals; k++) + { + p2 = leaf->portals[k]; + if (p2->removed) + continue; + if (p1->leaf == p2->leaf) + { + w = TryMergeWinding (&p1->winding, &p2->winding, p1->line); + if (w) + { + p1->winding = *w; + if (p1->hint && p2->hint) + hintsmerged++; + p1->hint |= p2->hint; + p2->removed = true; + nummerges++; + i--; + break; + } + } + } + if (k < leaf->numportals) + break; + } + } + printf("%6d portals merged\n", nummerges); + printf("%6d hint portals merged\n", hintsmerged); +} + +/* +============ +CountActivePortals +============ +*/ +int FRejectBuilder::CountActivePortals () +{ + int num, hints, j; + VPortal *p; + + num = 0; + hints = 0; + for (j = 0; j < numportals; j++) + { + p = portals + j; + if (p->removed) + continue; + if (p->hint) + hints++; + num++; + } + printf("%6d of %d active portals\n", num, numportals); + printf("%6d hint portals\n", hints); + return num; +} diff --git a/zdbsp_src/Unused/visflow.cc b/zdbsp_src/Unused/visflow.cc new file mode 100644 index 0000000000..517086e0b6 --- /dev/null +++ b/zdbsp_src/Unused/visflow.cc @@ -0,0 +1,1373 @@ +// An adaptation of the Quake vis utility. + +#include +#include +#include + +#include "zdbsp.h" +#include "nodebuild.h" +#include "rejectbuilder.h" +#include "templates.h" + +enum { SIDE_FRONT, SIDE_BACK, SIDE_ON }; + +/* + + each portal will have a list of all possible to see from first portal + + if (!thread->portalmightsee[portalnum]) + + portal mightsee + + for p2 = all other portals in leaf + get sperating planes + for all portals that might be seen by p2 + mark as unseen if not present in seperating plane + flood fill a new mightsee + save as passagemightsee + + + void CalcMightSee (leaf_t *leaf, +*/ + +int FRejectBuilder::CountBits (BYTE *bits, int numbits) +{ + int i; + int c; + + c = 0; + for (i=0 ; i>3] & (1<<(i&7)) ) + c++; + + return c; +} + +int c_fullskip; +int c_portalskip, c_leafskip; +int c_vistest, c_mighttest; + +int c_chop, c_nochop; + +int active; + +void FRejectBuilder::CheckStack (FLeaf *leaf, FThreadData *thread) +{ + PStack *p, *p2; + try + { + for (p = thread->pstack_head.next; p != NULL; p = p->next) + { + // _printf ("="); + if (p->leaf == leaf) + throw std::runtime_error("CheckStack: leaf recursion"); + for (p2 = thread->pstack_head.next; p2 != p; p2 = p2->next) + if (p2->leaf == p->leaf) + throw std::runtime_error("CheckStack: late leaf recursion"); + } + } + catch (std::runtime_error &msg) + { + printf ("%s\n", msg.what()); + } +// _printf ("\n"); +} + + +FRejectBuilder::FWinding *FRejectBuilder::AllocStackWinding (PStack *stack) const +{ + try + { + for (int i = 0; i < 3; i++) + { + if (stack->freewindings[i]) + { + stack->freewindings[i] = false; + return &stack->windings[i]; + } + } + + throw std::runtime_error("AllocStackWinding: failed"); + } + catch (std::runtime_error &msg) + { + printf ("%s\n", msg.what()); + } +} + +void FRejectBuilder::FreeStackWinding (FWinding *w, PStack *stack) const +{ + try + { + int i; + + i = w - stack->windings; + + if (i < 0 || i > 2) + return; // not from local + + if (stack->freewindings[i]) + throw std::runtime_error("FreeStackWinding: already free"); + stack->freewindings[i] = true; + } + catch (std::runtime_error &msg) + { + printf ("%s\n", msg.what()); + } +} + +/* +============== +VisChopWinding + +============== +*/ +FRejectBuilder::FWinding *FRejectBuilder::VisChopWinding (FWinding *in, PStack *stack, FLine *split) +{ + int side1, side2; + FPoint mid; + FWinding *neww; + + // determine sides for each point + side1 = PointOnSide (in->points[0], *split); + side2 = PointOnSide (in->points[1], *split); + + if (side1 <= 0 && side2 <= 0) + { // completely on front side + return in; + } + + if (side1 >= 0 && side2 >= 0) + { // completely on back side + FreeStackWinding (in, stack); + return NULL; + } + + neww = AllocStackWinding (stack); + + // generate a split point + double v2x = (double)in->points[0].x; + double v2y = (double)in->points[0].y; + double v2dx = (double)in->points[1].x - v2x; + double v2dy = (double)in->points[1].y - v2y; + double v1dx = (double)split->dx; + double v1dy = (double)split->dy; + + double den = v1dy*v2dx - v1dx*v2dy; + + if (den == 0.0) + { // parallel + return in; + } + + double v1x = (double)split->x; + double v1y = (double)split->y; + + double num = (v1x - v2x)*v1dy + (v2y - v1y)*v1dx; + double frac = num / den; + + mid.x = in->points[0].x + fixed_t(v2dx * frac); + mid.y = in->points[0].y + fixed_t(v2dy * frac); + + if (side1 <= 0) + { + neww->points[0] = in->points[0]; + neww->points[1] = mid; + } + else + { + neww->points[0] = mid; + neww->points[1] = in->points[1]; + } + + // free the original winding + FreeStackWinding (in, stack); + + return neww; +} + +/* +============== +ClipToSeperators + +Source, pass, and target are an ordering of portals. + +Generates seperating planes canidates by taking two points from source and one +point from pass, and clips target by them. + +If target is totally clipped away, that portal can not be seen through. + +Normal clip keeps target on the same side as pass, which is correct if the +order goes source, pass, target. If the order goes pass, source, target then +flipclip should be set. +============== +*/ +FRejectBuilder::FWinding *FRejectBuilder::ClipToSeperators + (FWinding *source, FWinding *pass, FWinding *target, bool flipclip, PStack *stack) +{ + int i, j; + FLine line; + int d; + bool fliptest; + + // check all combinations + for (i = 0; i < 2; i++) + { + // find a vertex of pass that makes a line that puts all of the + // vertexes of pass on the front side and all of the vertexes of + // source on the back side + for (j = 0; j < 2; j++) + { + line.x = source->points[i].x; + line.y = source->points[i].y; + line.dx = pass->points[j].x - line.x; + line.dy = pass->points[j].y - line.y; + + // + // find out which side of the generated seperating line has the + // source portal + // + fliptest = false; + d = PointOnSide (source->points[!i], line); + if (d > 0) + { // source is on the back side, so we want all + // pass and target on the front side + fliptest = false; + } + else if (d < 0) + { // source in on the front side, so we want all + // pass and target on the back side + fliptest = true; + } + else + { // colinear with source portal + continue; + } + + // + // flip the line if the source portal is backwards + // + if (fliptest) + { + line.Flip (); + } + + // + // if all of the pass portal points are now on the front side, + // this is the seperating line + // + d = PointOnSide (pass->points[!j], line); + if (d >= 0) + { // == 0: colinear with seperating plane + // > 0: points on back side; not a seperating plane + continue; + } + + // + // flip the line if we want the back side + // + if (flipclip) + { + line.Flip (); + } + +#ifdef SEPERATORCACHE + stack->seperators[flipclip][stack->numseperators[flipclip]] = line; + if (++stack->numseperators[flipclip] >= MAX_SEPERATORS) + throw exception("MAX_SEPERATORS"); +#endif + + // + // clip target by the seperating plane + // + target = VisChopWinding (target, stack, &line); + if (!target) + { // target is not visible + return NULL; + } + + break; // optimization by Antony Suter + } + } + + return target; +} + +/* +================== +RecursiveLeafFlow + +Flood fill through the leafs +If src_portal is NULL, this is the originating leaf +================== +*/ +void FRejectBuilder::RecursiveLeafFlow (int leafnum, FThreadData *thread, PStack *prevstack) +{ + PStack stack; + VPortal *p; + FLine backline; + FLeaf *leaf; + int i, j; + long *test, *might, *prevmight, *vis, more; + int pnum; + + thread->c_chains++; + + leaf = &leafs[leafnum]; +// CheckStack (leaf, thread); + + prevstack->next = &stack; + + stack.next = NULL; + stack.leaf = leaf; + stack.portal = NULL; + stack.depth = prevstack->depth + 1; + +#ifdef SEPERATORCACHE + stack.numseperators[0] = 0; + stack.numseperators[1] = 0; +#endif + + might = (long *)stack.mightsee; + vis = (long *)thread->base->portalvis; + + // check all portals for flowing into other leafs + for (i = 0; i < leaf->numportals; i++) + { + p = leaf->portals[i]; + if (p->removed) + continue; + pnum = p - portals; + + /* MrE: portal trace debug code + { + int portaltrace[] = {13, 16, 17, 37}; + pstack_t *s; + + s = &thread->pstack_head; + for (j = 0; s->next && j < sizeof(portaltrace)/sizeof(int) - 1; j++, s = s->next) + { + if (s->portal->num != portaltrace[j]) + break; + } + if (j >= sizeof(portaltrace)/sizeof(int) - 1) + { + if (p->num == portaltrace[j]) + n = 0; //traced through all the portals + } + } + */ + + if ( ! (prevstack->mightsee[pnum >> 3] & (1<<(pnum&7)) ) ) + { + continue; // can't possibly see it + } + + // if the portal can't see anything we haven't already seen, skip it + if (p->status == STAT_Done) + { + test = (long *)p->portalvis; + } + else + { + test = (long *)p->portalflood; + } + + more = 0; + prevmight = (long *)prevstack->mightsee; + for (j = 0; j < portallongs; j++) + { + might[j] = prevmight[j] & test[j]; + more |= (might[j] & ~vis[j]); + } + + if (!more && + (thread->base->portalvis[pnum>>3] & (1<<(pnum&7))) ) + { // can't see anything new + continue; + } + + // get line of portal and point into the neighbor leaf + backline = stack.portalline = p->line; + backline.Flip (); + +// c_portalcheck++; + + stack.portal = p; + stack.next = NULL; + stack.freewindings[0] = true; + stack.freewindings[1] = true; + stack.freewindings[2] = true; + + stack.pass = VisChopWinding (&p->winding, &stack, &thread->pstack_head.portalline); + if (!stack.pass) + { + continue; + } + + stack.source = VisChopWinding (prevstack->source, &stack, &backline); + if (!stack.source) + { + continue; + } + + if (!prevstack->pass) + { // the second leaf can only be blocked if coplanar + + // mark the portal as visible + thread->base->portalvis[pnum>>3] |= (1<<(pnum&7)); + + RecursiveLeafFlow (p->leaf, thread, &stack); + continue; + } + +#ifdef SEPERATORCACHE + if (stack.numseperators[0]) + { + for (n = 0; n < stack.numseperators[0]; n++) + { + stack.pass = VisChopWinding (stack.pass, &stack, &stack.seperators[0][n]); + if (!stack.pass) + break; // target is not visible + } + if (n < stack.numseperators[0]) + continue; + } + else + { + stack.pass = ClipToSeperators (prevstack->source, prevstack->pass, stack.pass, false, &stack); + } +#else + stack.pass = ClipToSeperators (stack.source, prevstack->pass, stack.pass, false, &stack); +#endif + if (!stack.pass) + continue; + +#ifdef SEPERATORCACHE + if (stack.numseperators[1]) + { + for (n = 0; n < stack.numseperators[1]; n++) + { + stack.pass = VisChopWinding (stack.pass, &stack, &stack.seperators[1][n]); + if (!stack.pass) + break; // target is not visible + } + } + else + { + stack.pass = ClipToSeperators (prevstack->pass, prevstack->source, stack.pass, true, &stack); + } +#else + stack.pass = ClipToSeperators (prevstack->pass, stack.source, stack.pass, true, &stack); +#endif + if (!stack.pass) + continue; + + // mark the portal as visible + thread->base->portalvis[pnum>>3] |= (1<<(pnum&7)); + + // flow through it for real + RecursiveLeafFlow (p->leaf, thread, &stack); + // + stack.next = NULL; + } +} + +/* +=============== +PortalFlow + +generates the portalvis bit vector +=============== +*/ +void FRejectBuilder::PortalFlow (int portalnum) +{ + FThreadData data; + int i; + VPortal *p; + int c_might, c_can; + +#ifdef MREDEBUG + printf("\r%6d", portalnum); +#endif + + p = sorted_portals[portalnum]; + + if (p->removed) + { + p->status = STAT_Done; + return; + } + + if (p->nummightsee == 0) + { + p->status = STAT_Done; + return; + } + + p->status = STAT_Working; + + c_might = p->nummightsee;//CountBits (p->portalflood, numportals); + + memset (&data, 0, sizeof(data)); + data.base = p; + + data.pstack_head.portal = p; + data.pstack_head.source = &p->winding; + data.pstack_head.portalline = p->line; + data.pstack_head.depth = 0; + for (i = 0; i < portallongs; i++) + { + ((long *)data.pstack_head.mightsee)[i] = ((long *)p->portalflood)[i]; + } + + RecursiveLeafFlow (p->leaf, &data, &data.pstack_head); + + p->status = STAT_Done; + + c_can = CountBits (p->portalvis, numportals); + + //printf ("portal:%4i mightsee:%4i cansee:%4i (%i chains)\n", + // (int)(p - portals), c_might, c_can, data.c_chains); +} + +/* +================== +RecursivePassageFlow +================== +*/ +void FRejectBuilder::RecursivePassageFlow (VPortal *portal, FThreadData *thread, PStack *prevstack) +{ + PStack stack; + VPortal *p; + FLeaf *leaf; + FPassage *passage, *nextpassage; + int i, j; + long *might, *vis, *prevmight, *cansee, *portalvis, more; + int pnum; + +// thread->c_chains++; + + leaf = &leafs[portal->leaf]; +// CheckStack (leaf, thread); + + prevstack->next = &stack; + + stack.next = NULL; +// stack.leaf = leaf; +// stack.portal = NULL; + stack.depth = prevstack->depth + 1; + + vis = (long *)thread->base->portalvis; + + passage = portal->passages; + nextpassage = passage; + // check all portals for flowing into other leafs + for (i = 0; i < leaf->numportals; i++, passage = nextpassage) + { + p = leaf->portals[i]; + if (p->removed) + continue; + nextpassage = passage->next; + pnum = p - portals; + + if ( ! (prevstack->mightsee[pnum >> 3] & (1<<(pnum&7)) ) ) + continue; // can't possibly see it + + prevmight = (long *)prevstack->mightsee; + cansee = (long *)passage->cansee; + might = (long *)stack.mightsee; + memcpy(might, prevmight, portalbytes); + portalvis = (p->status == STAT_Done) ? (long *)p->portalvis : (long *)p->portalflood; + more = 0; + for (j = 0; j < portallongs; j++) + { + if (*might) + { + *might &= *cansee++ & *portalvis++; + more |= (*might & ~vis[j]); + } + else + { + cansee++; + portalvis++; + } + might++; + } + + if (!more && + (thread->base->portalvis[pnum>>3] & (1<<(pnum&7))) ) + { // can't see anything new + continue; + } + +// stack.portal = p; + // mark the portal as visible + thread->base->portalvis[pnum>>3] |= (1<<(pnum&7)); + // flow through it for real + RecursivePassageFlow(p, thread, &stack); + // + stack.next = NULL; + } +} + +/* +=============== +PassageFlow +=============== +*/ +void FRejectBuilder::PassageFlow (int portalnum) +{ + FThreadData data; + int i; + VPortal *p; +// int c_might, c_can; + +#ifdef MREDEBUG + printf("\r%6d", portalnum); +#endif + + p = sorted_portals[portalnum]; + + if (p->removed) + { + p->status = STAT_Done; + return; + } + + p->status = STAT_Working; + +// c_might = CountBits (p->portalflood, numportals); + + memset (&data, 0, sizeof(data)); + data.base = p; + + data.pstack_head.portal = p; + data.pstack_head.source = &p->winding; + data.pstack_head.portalline = p->line; + data.pstack_head.depth = 0; + for (i = 0; i < portallongs; i++) + { + ((long *)data.pstack_head.mightsee)[i] = ((long *)p->portalflood)[i]; + } + + RecursivePassageFlow (p, &data, &data.pstack_head); + + p->status = STAT_Done; + + /* + c_can = CountBits (p->portalvis, numportals); + + qprintf ("portal:%4i mightsee:%4i cansee:%4i (%i chains)\n", + (int)(p - portals), c_might, c_can, data.c_chains); + */ +} + +/* +================== +RecursivePassagePortalFlow +================== +*/ +void FRejectBuilder::RecursivePassagePortalFlow (VPortal *portal, FThreadData *thread, PStack *prevstack) +{ + PStack stack; + VPortal *p; + FLeaf *leaf; + FLine backline; + FPassage *passage, *nextpassage; + int i, j; + long *might, *vis, *prevmight, *cansee, *portalvis, more; + int pnum; + +// thread->c_chains++; + + leaf = &leafs[portal->leaf]; +// CheckStack (leaf, thread); + + prevstack->next = &stack; + + stack.next = NULL; + stack.leaf = leaf; + stack.portal = NULL; + stack.depth = prevstack->depth + 1; + +#ifdef SEPERATORCACHE + stack.numseperators[0] = 0; + stack.numseperators[1] = 0; +#endif + + vis = (long *)thread->base->portalvis; + + passage = portal->passages; + nextpassage = passage; + // check all portals for flowing into other leafs + for (i = 0; i < leaf->numportals; i++, passage = nextpassage) + { + p = leaf->portals[i]; + if (p->removed) + continue; + nextpassage = passage->next; + pnum = p - portals; + + if ( ! (prevstack->mightsee[pnum >> 3] & (1<<(pnum&7)) ) ) + continue; // can't possibly see it + + prevmight = (long *)prevstack->mightsee; + cansee = (long *)passage->cansee; + might = (long *)stack.mightsee; + memcpy(might, prevmight, portalbytes); + portalvis = (p->status == STAT_Done) ? (long *) p->portalvis : (long *) p->portalflood; + more = 0; + for (j = 0; j < portallongs; j++) + { + if (*might) + { + *might &= *cansee++ & *portalvis++; + more |= (*might & ~vis[j]); + } + else + { + cansee++; + portalvis++; + } + might++; + } + + if (!more && + (thread->base->portalvis[pnum>>3] & (1<<(pnum&7))) ) + { // can't see anything new + continue; + } + + // get line of portal, point front into the neighbor leaf + backline = stack.portalline = p->line; + backline.Flip (); + +// c_portalcheck++; + + stack.portal = p; + stack.next = NULL; + stack.freewindings[0] = true; + stack.freewindings[1] = true; + stack.freewindings[2] = true; + + stack.pass = VisChopWinding (&p->winding, &stack, &thread->pstack_head.portalline); + if (!stack.pass) + continue; + + stack.source = VisChopWinding (prevstack->source, &stack, &backline); + if (!stack.source) + continue; + + if (!prevstack->pass) + { // the second leaf can only be blocked if colinear + + // mark the portal as visible + thread->base->portalvis[pnum>>3] |= (1<<(pnum&7)); + + RecursivePassagePortalFlow (p, thread, &stack); + continue; + } + +#ifdef SEPERATORCACHE + if (stack.numseperators[0]) + { + for (n = 0; n < stack.numseperators[0]; n++) + { + stack.pass = VisChopWinding (stack.pass, &stack, &stack.seperators[0][n]); + if (!stack.pass) + break; // target is not visible + } + if (n < stack.numseperators[0]) + continue; + } + else + { + stack.pass = ClipToSeperators (prevstack->source, prevstack->pass, stack.pass, false, &stack); + } +#else + stack.pass = ClipToSeperators (stack.source, prevstack->pass, stack.pass, false, &stack); +#endif + if (!stack.pass) + continue; + +#ifdef SEPERATORCACHE + if (stack.numseperators[1]) + { + for (n = 0; n < stack.numseperators[1]; n++) + { + stack.pass = VisChopWinding (stack.pass, &stack, &stack.seperators[1][n]); + if (!stack.pass) + break; // target is not visible + } + } + else + { + stack.pass = ClipToSeperators (prevstack->pass, prevstack->source, stack.pass, true, &stack); + } +#else + stack.pass = ClipToSeperators (prevstack->pass, stack.source, stack.pass, true, &stack); +#endif + if (!stack.pass) + continue; + + // mark the portal as visible + thread->base->portalvis[pnum>>3] |= (1<<(pnum&7)); + // flow through it for real + RecursivePassagePortalFlow(p, thread, &stack); + // + stack.next = NULL; + } +} + +/* +=============== +PassagePortalFlow +=============== +*/ +void FRejectBuilder::PassagePortalFlow (int portalnum) +{ + FThreadData data; + int i; + VPortal *p; +// int c_might, c_can; + +#ifdef MREDEBUG + printf("\r%6d", portalnum); +#endif + + p = sorted_portals[portalnum]; + + if (p->removed) + { + p->status = STAT_Done; + return; + } + + p->status = STAT_Working; + +// c_might = CountBits (p->portalflood, numportals); + + memset (&data, 0, sizeof(data)); + data.base = p; + + data.pstack_head.portal = p; + data.pstack_head.source = &p->winding; + data.pstack_head.portalline = p->line; + data.pstack_head.depth = 0; + for (i = 0; i < portallongs; i++) + { + ((long *)data.pstack_head.mightsee)[i] = ((long *)p->portalflood)[i]; + } + + RecursivePassagePortalFlow (p, &data, &data.pstack_head); + + p->status = STAT_Done; + + /* + c_can = CountBits (p->portalvis, numportals); + + qprintf ("portal:%4i mightsee:%4i cansee:%4i (%i chains)\n", + (int)(p - portals), c_might, c_can, data.c_chains); + */ +} + +FRejectBuilder::FWinding *FRejectBuilder::PassageChopWinding (FWinding *in, FWinding *out, FLine *split) +{ + int side1, side2; + FPoint mid; + FWinding *neww; + + // determine sides for each point + side1 = PointOnSide (in->points[0], *split); + side2 = PointOnSide (in->points[1], *split); + + if (side1 <= 0 && side2 <= 0) + { // completely on front side + return in; + } + + if (side1 >= 0 && side2 >= 0) + { // completely on back side + return NULL; + } + + neww = out; + + // generate a split point + double v2x = (double)in->points[0].x; + double v2y = (double)in->points[0].y; + double v2dx = (double)in->points[1].x - v2x; + double v2dy = (double)in->points[1].y - v2y; + double v1dx = (double)split->dx; + double v1dy = (double)split->dy; + + double den = v1dy*v2dx - v1dx*v2dy; + + if (den == 0.0) + { // parallel + return in; + } + + double v1x = (double)split->x; + double v1y = (double)split->y; + + double num = (v1x - v2x)*v1dy + (v2y - v1y)*v1dx; + double frac = num / den; + + mid.x = in->points[0].x + fixed_t(v2dx * frac); + mid.y = in->points[0].y + fixed_t(v2dy * frac); + + if (side1 <= 0) + { + neww->points[0] = in->points[0]; + neww->points[1] = mid; + } + else + { + neww->points[0] = mid; + neww->points[1] = in->points[1]; + } + + return neww; +} + +/* +=============== +AddSeperators +=============== +*/ +int FRejectBuilder::AddSeperators (FWinding *source, FWinding *pass, bool flipclip, + FLine *seperators, int maxseperators) +{ + int i, j; + FLine line; + int d; + int numseperators; + bool fliptest; + + numseperators = 0; + // check all combinations + for (i = 0; i < 2; i++) + { + try + { + // find a vertex of pass that makes a plane that puts all of the + // vertexes of pass on the front side and all of the vertexes of + // source on the back side + for (j = 0; j < 2; j++) + { + line.x = source->points[i].x; + line.y = source->points[i].y; + line.dx = pass->points[j].x - line.x; + line.dy = pass->points[j].y - line.y; + + // + // find out which side of the generated seperating plane has the + // source portal + // + fliptest = false; + d = PointOnSide (source->points[!i], line); + if (d > 0) + { // source is on the back side, so we want all + // pass and target on the front side + fliptest = false; + } + else if (d < 0) + { // source in on the front side, so we want all + // pass and target on the back side + fliptest = true; + } + else + { // colinear with source portal + continue; + } + + // + // flip the line if the source portal is backwards + // + if (fliptest) + { + line.Flip (); + } + + // + // if all of the pass portal points are now on the positive side, + // this is the seperating plane + // + d = PointOnSide (pass->points[!j], line); + if (d >= 0) + { // == 0: colinear with seperating plane + // > 0: points on back side; not a seperating plane + continue; + } + + // + // flip the line if we want the back side + // + if (flipclip) + { + line.Flip (); + } + + if (numseperators >= maxseperators) + throw std::runtime_error("max seperators"); + seperators[numseperators] = line; + numseperators++; + break; + } + } + catch (std::runtime_error &msg) + { + printf ("%s\n", msg.what()); + } + } + return numseperators; +} + +/* +=============== +CreatePassages + +MrE: create passages from one portal to all the portals in the leaf the portal leads to + every passage has a cansee bit string with all the portals that can be + seen through the passage +=============== +*/ +void FRejectBuilder::CreatePassages (int portalnum) +{ + int i, j, k, numseperators, numsee; + VPortal *portal, *p, *target; + FLeaf *leaf; + FPassage *passage, *lastpassage; + FLine seperators[MAX_SEPERATORS*2]; + FWinding in, out, *res; + +#ifdef MREDEBUG + printf("\r%6d", portalnum); +#endif + + portal = sorted_portals[portalnum]; + + if (portal->removed) + { + portal->status = STAT_Done; + return; + } + + lastpassage = NULL; + leaf = &leafs[portal->leaf]; + for (i = 0; i < leaf->numportals; i++) + { + target = leaf->portals[i]; + if (target->removed) + continue; + + passage = (FPassage *) malloc(sizeof(FPassage) + portalbytes); + memset(passage, 0, sizeof(FPassage) + portalbytes); + numseperators = AddSeperators(&portal->winding, &target->winding, false, seperators, MAX_SEPERATORS*2); + numseperators += AddSeperators(&target->winding, &portal->winding, true, &seperators[numseperators], MAX_SEPERATORS*2-numseperators); + + passage->next = NULL; + if (lastpassage) + lastpassage->next = passage; + else + portal->passages = passage; + lastpassage = passage; + + numsee = 0; + //create the passage->cansee + for (j = 0; j < numportals; j++) + { + p = &portals[j]; + if (p->removed) + continue; + if ( ! (target->portalflood[j >> 3] & (1<<(j&7)) ) ) + continue; + if ( ! (portal->portalflood[j >> 3] & (1<<(j&7)) ) ) + continue; + for (k = 0; k < numseperators; k++) + { + //check if completely on the back of the seperator line + if (PointOnSide (p->line, seperators[k]) > 0) + { + FPoint pt2 = p->line; + pt2.x += p->line.dx; + pt2.y += p->line.dy; + if (PointOnSide (pt2, seperators[k]) > 0) + { + break; + } + } + } + if (k < numseperators) + { + continue; + } + memcpy(&in, &p->winding, sizeof(FWinding)); + for (k = 0; k < numseperators; k++) + { + res = PassageChopWinding(&in, &out, &seperators[k]); + if (res == &out) + memcpy(&in, &out, sizeof(FWinding)); + if (res == NULL) + break; + } + if (k < numseperators) + continue; + passage->cansee[j >> 3] |= (1<<(j&7)); + numsee++; + } + } +} + +void FRejectBuilder::PassageMemory () +{ + int i, j, totalmem, totalportals; + VPortal *portal, *target; + FLeaf *leaf; + + totalmem = 0; + totalportals = 0; + for (i = 0; i < numportals; i++) + { + portal = sorted_portals[i]; + if (portal->removed) + continue; + leaf = &leafs[portal->leaf]; + for (j = 0; j < leaf->numportals; j++) + { + target = leaf->portals[j]; + if (target->removed) + continue; + totalmem += sizeof(FPassage) + portalbytes; + totalportals++; + } + } + printf("\n%7i average number of passages per leaf\n", totalportals / numportals); + printf("%7i MB required passage memory\n", totalmem >> 10 >> 10); +} + +/* +=============================================================================== + +This is a rough first-order aproximation that is used to trivially reject some +of the final calculations. + + +Calculates portalfront and portalflood bit vectors + +thinking about: + +typedef struct passage_s +{ + struct passage_s *next; + struct portal_s *to; + struct sep_s *seperators; + byte *mightsee; +} passage_t; + +typedef struct portal_s +{ + struct passage_s *passages; + int leaf; // leaf portal faces into +} portal_s; + +leaf = portal->leaf +clear +for all portals + + +calc portal visibility + clear bit vector + for all passages + passage visibility + + +for a portal to be visible to a passage, it must be on the front of +all seperating planes, and both portals must be behind the new portal + +=============================================================================== +*/ + +int c_flood, c_vis; + + +/* +================== +SimpleFlood + +================== +*/ +void FRejectBuilder::SimpleFlood (VPortal *srcportal, int leafnum) +{ + int i; + FLeaf *leaf; + VPortal *p; + int pnum; + + leaf = &leafs[leafnum]; + + for (i = 0; i < leaf->numportals; i++) + { + p = leaf->portals[i]; + if (p->removed) + continue; + pnum = p - portals; + if ((srcportal->portalfront[pnum>>3] & (1<<(pnum&7))) && + !(srcportal->portalflood[pnum>>3] & (1<<(pnum&7))) ) + { + srcportal->portalflood[pnum>>3] |= (1<<(pnum&7)); + SimpleFlood (srcportal, p->leaf); + } + } +} + +/* +============== +BasePortalVis +============== +*/ +void FRejectBuilder::BasePortalVis (int portalnum) +{ + int j, p1, p2; + VPortal *tp, *p; + + p = portals+portalnum; + + if (p->removed) + return; + + p->portalfront = new BYTE[portalbytes]; + memset (p->portalfront, 0, portalbytes); + + p->portalflood = new BYTE[portalbytes]; + memset (p->portalflood, 0, portalbytes); + + p->portalvis = new BYTE[portalbytes]; + memset (p->portalvis, 0, portalbytes); + + for (j = 0, tp = portals; j < numportals; j++, tp++) + { + if (j == portalnum) + continue; + if (tp->removed) + continue; + + //p->portalfront[j>>3] |= (1<<(j&7)); + //continue; + + // The target portal must be in front of this one + if ((p1 = PointOnSide (tp->winding.points[0], p->line)) > 0 || + (p2 = PointOnSide (tp->winding.points[1], p->line)) > 0) + { + continue; + } + + // Portals must not be colinear + if ((p1 | p2) == 0) + { + continue; + } + + // This portal must be behind the target portal + if (PointOnSide (p->winding.points[0], tp->line) < 0 || + PointOnSide (p->winding.points[1], tp->line) < 0) + { + continue; + } + + p->portalfront[j>>3] |= (1<<(j&7)); + } + + SimpleFlood (p, p->leaf); + + p->nummightsee = CountBits (p->portalflood, numportals); +// _printf ("portal %i: %i mightsee\n", portalnum, p->nummightsee); + c_flood += p->nummightsee; +} + + + + + +/* +=============================================================================== + +This is a second order aproximation + +Calculates portalvis bit vector + +WAAAAAAY too slow. + +=============================================================================== +*/ + +/* +================== +RecursiveLeafBitFlow + +================== +*/ +void FRejectBuilder::RecursiveLeafBitFlow (int leafnum, BYTE *mightsee, BYTE *cansee) +{ + VPortal *p; + FLeaf *leaf; + int i, j; + long more; + int pnum; + BYTE newmight[MAX_PORTALS/8]; + + leaf = &leafs[leafnum]; + + // check all portals for flowing into other leafs + for (i = 0; i < leaf->numportals; i++) + { + p = leaf->portals[i]; + if (p->removed) + continue; + pnum = p - portals; + + // if some previous portal can't see it, skip + if (! (mightsee[pnum>>3] & (1<<(pnum&7)) ) ) + continue; + + // if this portal can see some portals we mightsee, recurse + more = 0; + for (j = 0; j < portallongs; j++) + { + ((long *)newmight)[j] = ((long *)mightsee)[j] & ((long *)p->portalflood)[j]; + more |= ((long *)newmight)[j] & ~((long *)cansee)[j]; + } + + if (!more) + continue; // can't see anything new + + cansee[pnum>>3] |= (1<<(pnum&7)); + + RecursiveLeafBitFlow (p->leaf, newmight, cansee); + } +} + +/* +============== +BetterPortalVis +============== +*/ +void FRejectBuilder::BetterPortalVis (int portalnum) +{ + VPortal *p; + + p = portals+portalnum; + + if (p->removed) + return; + + RecursiveLeafBitFlow (p->leaf, p->portalflood, p->portalvis); + + // build leaf vis information + p->nummightsee = CountBits (p->portalvis, numportals); + c_vis += p->nummightsee; +} + + diff --git a/zdbsp_src/zipbin.bat b/zdbsp_src/Unused/zipbin.bat similarity index 100% rename from zdbsp_src/zipbin.bat rename to zdbsp_src/Unused/zipbin.bat diff --git a/zdbsp_src/processor.cc b/zdbsp_src/processor.cc index be488be3b9..a3894a99a7 100644 --- a/zdbsp_src/processor.cc +++ b/zdbsp_src/processor.cc @@ -19,7 +19,7 @@ */ #include "processor.h" -//#include "rejectbuilder.h" +#include "rejectbuilder.h" enum { @@ -653,10 +653,13 @@ void FProcessor::Write (FWadWriter &out) switch (RejectMode) { case ERM_Rebuild: - //FRejectBuilder reject (Level); - //Level.Reject = reject.GetReject (); - printf (" Rebuilding the reject is unsupported.\n"); - // Intentional fall-through + { + FRejectBuilder reject(Level); + Level.Reject = reject.GetReject(); + //printf (" Rebuilding the reject is unsupported.\n"); + // Intentional fall-through + break; + } case ERM_DontTouch: { @@ -692,9 +695,11 @@ void FProcessor::Write (FWadWriter &out) break; case ERM_CreateZeroes: - Level.Reject = new BYTE[Level.RejectSize]; - memset (Level.Reject, 0, Level.RejectSize); - break; + { + Level.Reject = new BYTE[Level.RejectSize]; + memset (Level.Reject, 0, Level.RejectSize); + break; + } } } diff --git a/zdbsp_src/processor_udmf-2.cc b/zdbsp_src/processor_udmf-2.cc deleted file mode 100644 index db0c9d40c7..0000000000 --- a/zdbsp_src/processor_udmf-2.cc +++ /dev/null @@ -1,606 +0,0 @@ -/* - Reads and writes UDMF maps - Copyright (C) 2009 Christoph Oelckers - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - -*/ - - -#include -#include "processor.h" -#include "sc_man.h" - -typedef double real64; -typedef unsigned int uint32; -typedef signed int int32; -#include "xs_Float.h" - - -class StringBuffer -{ - const static size_t BLOCK_SIZE = 100000; - const static size_t BLOCK_ALIGN = sizeof(size_t); - - TDeletingArray blocks; - size_t currentindex; - - char *Alloc(size_t size) - { - if (currentindex + size >= BLOCK_SIZE) - { - // Block is full - get a new one! - char *newblock = new char[BLOCK_SIZE]; - blocks.Push(newblock); - currentindex = 0; - } - size = (size + BLOCK_ALIGN-1) &~ (BLOCK_ALIGN-1); - char *p = blocks[blocks.Size()-1] + currentindex; - currentindex += size; - return p; - } -public: - - StringBuffer() - { - currentindex = BLOCK_SIZE; - } - - char * Copy(const char * p) - { - return p != NULL? strcpy(Alloc(strlen(p)+1) , p) : NULL; - } -}; - -StringBuffer stbuf; - - -//=========================================================================== -// -// Parses a 'key = value;' line of the map -// -//=========================================================================== - -const char *FProcessor::ParseKey(const char *&value) -{ - SC_MustGetString(); - const char *key = stbuf.Copy(sc_String); - SC_MustGetStringName("="); - - sc_Number = INT_MIN; - sc_Float = DBL_MIN; - if (!SC_CheckFloat()) - { - SC_MustGetString(); - } - value = stbuf.Copy(sc_String); - SC_MustGetStringName(";"); - return key; -} - -bool FProcessor::CheckKey(const char *&key, const char *&value) -{ - SC_SavePos(); - SC_MustGetString(); - if (SC_CheckString("=")) - { - SC_RestorePos(); - key = ParseKey(value); - return true; - } - SC_RestorePos(); - return false; -} - -int CheckInt(const char *key) -{ - if (sc_Number == INT_MIN) - { - SC_ScriptError("Integer value expected for key '%s'", key); - } - return sc_Number; -} - -double CheckFloat(const char *key) -{ - if (sc_Float == DBL_MIN) - { - SC_ScriptError("Floating point value expected for key '%s'", key); - } - return sc_Float; -} - -fixed_t CheckFixed(const char *key) -{ - double val = CheckFloat(key); - if (val < -32768 || val > 32767) - { - SC_ScriptError("Fixed point value is out of range for key '%s'\n\t%.2f should be within [-32768,32767]", key, val / 65536); - } - return xs_Fix<16>::ToFix(val); -} - -//=========================================================================== -// -// Parse a thing block -// -//=========================================================================== - -void FProcessor::ParseThing(IntThing *th) -{ - SC_MustGetStringName("{"); - while (!SC_CheckString("}")) - { - const char *value; - const char *key = ParseKey(value); - - // The only properties we need from a thing are - // x, y, angle and type. - - if (!stricmp(key, "x")) - { - th->x = CheckFixed(key); - } - else if (!stricmp(key, "y")) - { - th->y = CheckFixed(key); - } - if (!stricmp(key, "angle")) - { - th->angle = (short)CheckInt(key); - } - if (!stricmp(key, "type")) - { - th->type = (short)CheckInt(key); - } - - // now store the key in its unprocessed form - UDMFKey k = {key, value}; - th->props.Push(k); - } -} - -//=========================================================================== -// -// Parse a linedef block -// -//=========================================================================== - -void FProcessor::ParseLinedef(IntLineDef *ld) -{ - SC_MustGetStringName("{"); - ld->v1 = ld->v2 = ld->sidenum[0] = ld->sidenum[1] = NO_INDEX; - ld->special = 0; - while (!SC_CheckString("}")) - { - const char *value; - const char *key = ParseKey(value); - - if (!stricmp(key, "v1")) - { - ld->v1 = CheckInt(key); - continue; // do not store in props - } - else if (!stricmp(key, "v2")) - { - ld->v2 = CheckInt(key); - continue; // do not store in props - } - else if (Extended && !stricmp(key, "special")) - { - ld->special = CheckInt(key); - } - else if (Extended && !stricmp(key, "arg0")) - { - ld->args[0] = CheckInt(key); - } - if (!stricmp(key, "sidefront")) - { - ld->sidenum[0] = CheckInt(key); - continue; // do not store in props - } - else if (!stricmp(key, "sideback")) - { - ld->sidenum[1] = CheckInt(key); - continue; // do not store in props - } - - // now store the key in its unprocessed form - UDMFKey k = {key, value}; - ld->props.Push(k); - } -} - -//=========================================================================== -// -// Parse a sidedef block -// -//=========================================================================== - -void FProcessor::ParseSidedef(IntSideDef *sd) -{ - SC_MustGetStringName("{"); - sd->sector = NO_INDEX; - while (!SC_CheckString("}")) - { - const char *value; - const char *key = ParseKey(value); - - if (!stricmp(key, "sector")) - { - sd->sector = CheckInt(key); - continue; // do not store in props - } - - // now store the key in its unprocessed form - UDMFKey k = {key, value}; - sd->props.Push(k); - } -} - -//=========================================================================== -// -// Parse a sidedef block -// -//=========================================================================== - -void FProcessor::ParseSector(IntSector *sec) -{ - SC_MustGetStringName("{"); - while (!SC_CheckString("}")) - { - const char *value; - const char *key = ParseKey(value); - - // No specific sector properties are ever used by the node builder - // so everything can go directly to the props array. - - // now store the key in its unprocessed form - UDMFKey k = {key, value}; - sec->props.Push(k); - } -} - -//=========================================================================== -// -// parse a vertex block -// -//=========================================================================== - -void FProcessor::ParseVertex(WideVertex *vt, IntVertex *vtp) -{ - vt->x = vt->y = 0; - SC_MustGetStringName("{"); - while (!SC_CheckString("}")) - { - const char *value; - const char *key = ParseKey(value); - - if (!stricmp(key, "x")) - { - vt->x = CheckFixed(key); - } - else if (!stricmp(key, "y")) - { - vt->y = CheckFixed(key); - } - - // now store the key in its unprocessed form - UDMFKey k = {key, value}; - vtp->props.Push(k); - } -} - - -//=========================================================================== -// -// parses global map properties -// -//=========================================================================== - -void FProcessor::ParseMapProperties() -{ - const char *key, *value; - - // all global keys must come before the first map element. - - while (CheckKey(key, value)) - { - if (!stricmp(key, "namespace")) - { - // all unknown namespaces are assumed to be standard. - Extended = !stricmp(value, "\"ZDoom\"") || !stricmp(value, "\"Hexen\"") || !stricmp(value, "\"Vavoom\""); - } - - // now store the key in its unprocessed form - UDMFKey k = {key, value}; - Level.props.Push(k); - } -} - - -//=========================================================================== -// -// Main parsing function -// -//=========================================================================== - -void FProcessor::ParseTextMap(int lump) -{ - char *buffer; - int buffersize; - TArray Vertices; - - ReadLump (Wad, lump, buffer, buffersize); - SC_OpenMem("TEXTMAP", buffer, buffersize); - - SC_SetCMode(true); - ParseMapProperties(); - - while (SC_GetString()) - { - if (SC_Compare("thing")) - { - IntThing *th = &Level.Things[Level.Things.Reserve(1)]; - ParseThing(th); - } - else if (SC_Compare("linedef")) - { - IntLineDef *ld = &Level.Lines[Level.Lines.Reserve(1)]; - ParseLinedef(ld); - } - else if (SC_Compare("sidedef")) - { - IntSideDef *sd = &Level.Sides[Level.Sides.Reserve(1)]; - ParseSidedef(sd); - } - else if (SC_Compare("sector")) - { - IntSector *sec = &Level.Sectors[Level.Sectors.Reserve(1)]; - ParseSector(sec); - } - else if (SC_Compare("vertex")) - { - WideVertex *vt = &Vertices[Vertices.Reserve(1)]; - IntVertex *vtp = &Level.VertexProps[Level.VertexProps.Reserve(1)]; - vt->index = Vertices.Size(); - ParseVertex(vt, vtp); - } - } - Level.Vertices = new WideVertex[Vertices.Size()]; - Level.NumVertices = Vertices.Size(); - memcpy(Level.Vertices, &Vertices[0], Vertices.Size() * sizeof(WideVertex)); - SC_Close(); - delete[] buffer; -} - - -//=========================================================================== -// -// parse an UDMF map -// -//=========================================================================== - -void FProcessor::LoadUDMF() -{ - ParseTextMap(Lump+1); -} - -//=========================================================================== -// -// writes a property list -// -//=========================================================================== - -void FProcessor::WriteProps(FWadWriter &out, TArray &props) -{ - for(unsigned i=0; i< props.Size(); i++) - { - out.AddToLump(props[i].key, (int)strlen(props[i].key)); - out.AddToLump(" = ", 3); - out.AddToLump(props[i].value, (int)strlen(props[i].value)); - out.AddToLump(";\n", 2); - } -} - -//=========================================================================== -// -// writes an integer property -// -//=========================================================================== - -void FProcessor::WriteIntProp(FWadWriter &out, const char *key, int value) -{ - char buffer[20]; - - out.AddToLump(key, (int)strlen(key)); - out.AddToLump(" = ", 3); - sprintf(buffer, "%d;\n", value); - out.AddToLump(buffer, (int)strlen(buffer)); -} - -//=========================================================================== -// -// writes a UDMF thing -// -//=========================================================================== - -void FProcessor::WriteThingUDMF(FWadWriter &out, IntThing *th, int num) -{ - out.AddToLump("thing", 5); - if (WriteComments) - { - char buffer[32]; - int len = sprintf(buffer, " // %d", num); - out.AddToLump(buffer, len); - } - out.AddToLump("\n{\n", 3); - WriteProps(out, th->props); - out.AddToLump("}\n\n", 3); -} - -//=========================================================================== -// -// writes a UDMF linedef -// -//=========================================================================== - -void FProcessor::WriteLinedefUDMF(FWadWriter &out, IntLineDef *ld, int num) -{ - out.AddToLump("linedef", 7); - if (WriteComments) - { - char buffer[32]; - int len = sprintf(buffer, " // %d", num); - out.AddToLump(buffer, len); - } - out.AddToLump("\n{\n", 3); - WriteIntProp(out, "v1", ld->v1); - WriteIntProp(out, "v2", ld->v2); - if (ld->sidenum[0] != NO_INDEX) WriteIntProp(out, "sidefront", ld->sidenum[0]); - if (ld->sidenum[1] != NO_INDEX) WriteIntProp(out, "sideback", ld->sidenum[1]); - WriteProps(out, ld->props); - out.AddToLump("}\n\n", 3); -} - -//=========================================================================== -// -// writes a UDMF sidedef -// -//=========================================================================== - -void FProcessor::WriteSidedefUDMF(FWadWriter &out, IntSideDef *sd, int num) -{ - out.AddToLump("sidedef", 7); - if (WriteComments) - { - char buffer[32]; - int len = sprintf(buffer, " // %d", num); - out.AddToLump(buffer, len); - } - out.AddToLump("\n{\n", 3); - WriteIntProp(out, "sector", sd->sector); - WriteProps(out, sd->props); - out.AddToLump("}\n\n", 3); -} - -//=========================================================================== -// -// writes a UDMF sector -// -//=========================================================================== - -void FProcessor::WriteSectorUDMF(FWadWriter &out, IntSector *sec, int num) -{ - out.AddToLump("sector", 6); - if (WriteComments) - { - char buffer[32]; - int len = sprintf(buffer, " // %d", num); - out.AddToLump(buffer, len); - } - out.AddToLump("\n{\n", 3); - WriteProps(out, sec->props); - out.AddToLump("}\n\n", 3); -} - -//=========================================================================== -// -// writes a UDMF vertex -// -//=========================================================================== - -void FProcessor::WriteVertexUDMF(FWadWriter &out, IntVertex *vt, int num) -{ - out.AddToLump("vertex", 6); - if (WriteComments) - { - char buffer[32]; - int len = sprintf(buffer, " // %d", num); - out.AddToLump(buffer, len); - } - out.AddToLump("\n{\n", 3); - WriteProps(out, vt->props); - out.AddToLump("}\n\n", 3); -} - -//=========================================================================== -// -// writes a UDMF text map -// -//=========================================================================== - -void FProcessor::WriteTextMap(FWadWriter &out) -{ - out.StartWritingLump("TEXTMAP"); - WriteProps(out, Level.props); - for(int i = 0; i < Level.NumThings(); i++) - { - WriteThingUDMF(out, &Level.Things[i], i); - } - - for(int i = 0; i < Level.NumOrgVerts; i++) - { - WideVertex *vt = &Level.Vertices[i]; - if (vt->index <= 0) - { - // not valid! - throw std::runtime_error("Invalid vertex data."); - } - WriteVertexUDMF(out, &Level.VertexProps[vt->index-1], i); - } - - for(int i = 0; i < Level.NumLines(); i++) - { - WriteLinedefUDMF(out, &Level.Lines[i], i); - } - - for(int i = 0; i < Level.NumSides(); i++) - { - WriteSidedefUDMF(out, &Level.Sides[i], i); - } - - for(int i = 0; i < Level.NumSectors(); i++) - { - WriteSectorUDMF(out, &Level.Sectors[i], i); - } -} - -//=========================================================================== -// -// writes an UDMF map -// -//=========================================================================== - -void FProcessor::WriteUDMF(FWadWriter &out) -{ - out.CopyLump (Wad, Lump); - WriteTextMap(out); - if (ForceCompression) WriteGLBSPZ (out, "ZNODES"); - else WriteGLBSPX (out, "ZNODES"); - - // copy everything except existing nodes, blockmap and reject - for(int i=Lump+2; stricmp(Wad.LumpName(i), "ENDMAP") && i < Wad.NumLumps(); i++) - { - const char *lumpname = Wad.LumpName(i); - if (stricmp(lumpname, "ZNODES") && - stricmp(lumpname, "BLOCKMAP") && - stricmp(lumpname, "REJECT")) - { - out.CopyLump(Wad, i); - } - } - out.CreateLabel("ENDMAP"); -} diff --git a/zdbsp_src/rejectbuilder.cc b/zdbsp_src/rejectbuilder.cc new file mode 100644 index 0000000000..e18e85e47d --- /dev/null +++ b/zdbsp_src/rejectbuilder.cc @@ -0,0 +1,278 @@ +// This is the same algorithm used by DoomBSP: +// +// Represent each sector by its bounding box. Then for each pair of +// sectors, see if any chains of one-sided lines can walk from one +// side of the convex hull for that pair to the other side. +// +// It works, but it's far from being perfect. It's quite easy for +// this algorithm to consider two sectors as being visible from +// each other when they are really not. But it won't erroneously +// flag two sectors as obstructed when they're really not, and that's +// the only thing that really matters when building a REJECT lump. + +#include +#include + +#include "zdbsp.h" +#include "nodebuild.h" +#include "rejectbuilder.h" +#include "templates.h" + +FRejectBuilder::FRejectBuilder (FLevel &level) + : Level (level) +{ + int i; + + i = Level.NumGLSubsectors * Level.NumGLSubsectors; + SubSeeMatrix = new BYTE[i]; + memset (SubSeeMatrix, 0, i); + + SegSubsectors = new WORD[Level.NumGLSegs]; + for (i = 0; i < Level.NumGLSubsectors; ++i) + { + for (int j = 0; j < Level.GLSubsectors[i].numlines; ++j) + { + SegSubsectors[j + Level.GLSubsectors[i].firstline] = i; + } + } + + BuildReject (); +} + +FRejectBuilder::~FRejectBuilder () +{ + delete[] SubSeeMatrix; + delete[] SegSubsectors; +} + +BYTE *FRejectBuilder::GetReject () +{ + int i, j; + + int rejectSize = (Level.NumSectors()*Level.NumSectors() + 7) / 8; + BYTE *reject = new BYTE[rejectSize]; + memset (reject, 0xff, rejectSize); + + int pvs_size = (Level.NumGLSubsectors * Level.NumGLSubsectors) + 7 / 8; + Level.GLPVS = new BYTE[pvs_size]; + Level.GLPVSSize = pvs_size; + memset (Level.GLPVS, 0, pvs_size); + + for (i = 0; i < Level.NumGLSubsectors; ++i) + { + int row = i*Level.NumGLSubsectors; + int sector1 = + Level.Sides[ + Level.Lines[ + Level.GLSegs[ + Level.GLSubsectors[i].firstline].linedef] + .sidenum[ + Level.GLSegs[ + Level.GLSubsectors[i].firstline].side]] + .sector; + int srow = sector1*Level.NumSectors(); + for (j = 0; j < Level.NumGLSubsectors; ++j) + { + if (SubSeeMatrix[row + j]) + { + int sector2 = + Level.Sides[ + Level.Lines[ + Level.GLSegs[ + Level.GLSubsectors[j].firstline].linedef] + .sidenum[ + Level.GLSegs[ + Level.GLSubsectors[j].firstline].side]] + .sector; + int l = (srow + sector2) >> 3; + int r = (srow + sector2) & 7; + reject[l] &= ~(1 << r); + + l = (row + j) >> 3; + r = (row + j) & 7; + Level.GLPVS[l] |= 1 << r; + } + } + } + + return reject; +} + +void FRejectBuilder::BuildReject () +{ + int s1, s2; + + for (s1 = 0; s1 < Level.NumGLSubsectors; ++s1) + { + //printf (" Reject: %3d%%\r",s1*100/Level.NumGLSubsectors); + printf ("%d/%d\r", s1, Level.NumGLSubsectors); + + // A subsector can always see itself + SourceRow = s1 * Level.NumGLSubsectors; + SubSeeMatrix[SourceRow + s1] = 1; + + WORD pusher = s1; + + for (s2 = 0; s2 < Level.GLSubsectors[s1].numlines; ++s2) + { + int segnum = s2 + Level.GLSubsectors[s1].firstline; + const MapSegGL *seg = (const MapSegGL*)&Level.GLSegs[segnum]; + if (seg->partner == NO_INDEX) + { + continue; + } + + SourceSeg = segnum; + SegRow = segnum * Level.NumGLSegs; + + TracePath (s1, seg); + } + } + printf (" Reject: 100%%\n"); +} + +inline const WideVertex *FRejectBuilder::GetVertex (WORD vertnum) +{ + if (vertnum & 0x8000) + { + return &Level.GLVertices[vertnum & 0x7fff]; + } + else + { + return &Level.Vertices[vertnum]; + } +} + +void FRejectBuilder::TracePath (int subsector, const MapSegGL *window) +{ + // Neighboring subsectors can always see each other + SubSeeMatrix[SourceRow + SegSubsectors[window->partner]] = 1; + const MapSubsector *backsub = (const MapSubsector*)&Level.GLSubsectors[SegSubsectors[window->partner]]; + + Portal source; + + source.Subsector = backsub; + source.Left = GetVertex (window->v1); + source.Right = GetVertex (window->v2); + + PortalStack.Clear (); + PortalStack.Push (source); + + fixed_t wdx = source.Right->x - source.Left->x; + fixed_t wdy = source.Right->y - source.Left->y; + +// printf ("start window %d\n", window - Level.GLSegs); + + for (int i = 0; i < backsub->numlines; ++i) + { + int segnum = backsub->firstline + i; + + if (segnum == window->partner) + { + continue; + } + + const MapSegGL *cseg = (const MapSegGL*)&Level.GLSegs[segnum]; + + if (cseg->partner == NO_INDEX) + { + continue; + } + + const WideVertex *cv1 = GetVertex (cseg->v1); + const WideVertex *cv2 = GetVertex (cseg->v2); + + if (FNodeBuilder::PointOnSide (cv1->x, cv1->y, source.Left->x, source.Left->y, wdx, wdy) == 0 && + FNodeBuilder::PointOnSide (cv2->x, cv2->y, source.Left->x, source.Left->y, wdx, wdy) == 0) + { + continue; + } + + TracePathDeep (cseg); + } +} + +void FRejectBuilder::TracePathDeep (const MapSegGL *window) +{ + SubSeeMatrix[SourceRow + SegSubsectors[window->partner]] = 1; + const MapSubsector *backsub = (const MapSubsector*)&Level.GLSubsectors[SegSubsectors[window->partner]]; + size_t j; + + for (j = PortalStack.Size(); j-- > 0; ) + { + if (PortalStack[j].Subsector == backsub) + { + return; + } + } + + Portal entrance; + + entrance.Subsector = backsub; + entrance.Left = GetVertex (window->v1); + entrance.Right = GetVertex (window->v2); + PortalStack.Push (entrance); + + fixed_t wdx = entrance.Right->x - entrance.Left->x; + fixed_t wdy = entrance.Right->y - entrance.Left->y; + + //printf ("deep through %d\n", window - Level.GLSegs); + + for (int i = 0; i < backsub->numlines; ++i) + { + int segnum = backsub->firstline + i; + + if (segnum == window->partner) + { + continue; + } + + const MapSegGL *cseg = (const MapSegGL*)&Level.GLSegs[segnum]; + + if (cseg->partner == NO_INDEX) + { + continue; + } + + const WideVertex *cv1 = GetVertex (cseg->v1); + const WideVertex *cv2 = GetVertex (cseg->v2); + + if (FNodeBuilder::PointOnSide (cv1->x, cv1->y, entrance.Left->x, entrance.Left->y, wdx, wdy) <= 0 && + FNodeBuilder::PointOnSide (cv2->x, cv2->y, entrance.Left->x, entrance.Left->y, wdx, wdy) <= 0) + { + continue; + } + + fixed_t leftx = PortalStack[0].Left->x; + fixed_t lefty = PortalStack[0].Left->y; + fixed_t rightx = PortalStack[0].Right->x; + fixed_t righty = PortalStack[0].Right->y; + + fixed_t leftdx = cv1->x - leftx; + fixed_t leftdy = cv1->y - lefty; + fixed_t rightdx = rightx - cv2->x; + fixed_t rightdy = righty - cv2->y; + + if (FNodeBuilder::PointOnSide (cv1->x, cv1->y, rightx, righty, rightdx, rightdy) >= 0 || + FNodeBuilder::PointOnSide (cv2->x, cv2->y, leftx, lefty, leftdx, leftdy) >= 0) + { + continue; + } + + for (j = PortalStack.Size(); j-- > 1; ) + { + if (FNodeBuilder::PointOnSide (PortalStack[j].Left->x, PortalStack[j].Left->y, + rightx, righty, rightdx, rightdy) >= 0 || + FNodeBuilder::PointOnSide (PortalStack[j].Right->x, PortalStack[j].Right->y, + leftx, lefty, leftdx, leftdy) >= 0) + { + break; + } + } + if (j == 0) + { + TracePathDeep (cseg); + } + } + PortalStack.Pop (entrance); +} diff --git a/zdbsp_src/rejectbuilder.h b/zdbsp_src/rejectbuilder.h new file mode 100644 index 0000000000..2375ef1293 --- /dev/null +++ b/zdbsp_src/rejectbuilder.h @@ -0,0 +1,40 @@ +#include "zdbsp.h" +#include "tarray.h" +#include "doomdata.h" + +class FRejectBuilder +{ +public: + FRejectBuilder (FLevel &level); + ~FRejectBuilder (); + + BYTE *GetReject (); + +private: + struct Portal + { + const MapSubsector *Subsector; + const WideVertex *Left; + const WideVertex *Right; + }; + enum ESegSeeStatus + { + MIGHT_SEE, + CAN_SEE, + CANNOT_SEE + }; + + void BuildReject (); + void TracePath (int subsector, const MapSegGL *window); + void TracePathDeep (const MapSegGL *window); + inline const WideVertex *GetVertex (WORD vertnum); + + BYTE *SubSeeMatrix; + WORD *SegSubsectors; + TArray PortalStack; + + FLevel &Level; + + int SourceRow; + int SourceSeg, SegRow; +}; From 5686846fee42095c214c38359968edf53dd862a4 Mon Sep 17 00:00:00 2001 From: Dasho Danger Date: Sat, 20 Mar 2021 19:03:41 +0430 Subject: [PATCH 10/20] GL-node based Reject Builder working; need to experiment more with optimal ZDBSP options to pass to it --- Makefile | 2 + gui/g_doom.cc | 2 +- zdbsp_src/Unused/rejectbuilder.cc-nogl | 331 ++++++ zdbsp_src/Unused/rejectbuilder.cc.bak | 278 +++++ zdbsp_src/Unused/rejectbuilder.h.bak | 40 + zdbsp_src/rejectbuilder.cc | 304 +++--- zdbsp_src/rejectbuilder.h | 207 +++- zdbsp_src/vis.cc | 588 ++++++++++ zdbsp_src/visflow.cc | 1373 ++++++++++++++++++++++++ 9 files changed, 2923 insertions(+), 202 deletions(-) create mode 100644 zdbsp_src/Unused/rejectbuilder.cc-nogl create mode 100644 zdbsp_src/Unused/rejectbuilder.cc.bak create mode 100644 zdbsp_src/Unused/rejectbuilder.h.bak create mode 100644 zdbsp_src/vis.cc create mode 100644 zdbsp_src/visflow.cc diff --git a/Makefile b/Makefile index b79e4164f7..4d4bce217d 100644 --- a/Makefile +++ b/Makefile @@ -174,6 +174,8 @@ ZDBSP_OBJS= \ $(OBJ_DIR)/zdbsp/zdwad.o \ $(OBJ_DIR)/zdbsp/nodebuild.o \ $(OBJ_DIR)/zdbsp/rejectbuilder.o \ + $(OBJ_DIR)/zdbsp/vis.o \ + $(OBJ_DIR)/zdbsp/visflow.o \ $(OBJ_DIR)/zdbsp/nodebuild_events.o \ $(OBJ_DIR)/zdbsp/nodebuild_extract.o \ $(OBJ_DIR)/zdbsp/nodebuild_gl.o \ diff --git a/gui/g_doom.cc b/gui/g_doom.cc index 502a7cc3c3..7bebdcae6e 100644 --- a/gui/g_doom.cc +++ b/gui/g_doom.cc @@ -887,7 +887,7 @@ static bool DM_BuildNodes(const char *filename, const char *out_name) } zdbsp_options options; options.build_nodes = true; - options.build_gl_nodes = false; + options.build_gl_nodes = true; options.reject_mode = ERM_Rebuild; options.check_polyobjs = true; options.compress_nodes = true; diff --git a/zdbsp_src/Unused/rejectbuilder.cc-nogl b/zdbsp_src/Unused/rejectbuilder.cc-nogl new file mode 100644 index 0000000000..4871607f9f --- /dev/null +++ b/zdbsp_src/Unused/rejectbuilder.cc-nogl @@ -0,0 +1,331 @@ +// This is the same algorithm used by DoomBSP: +// +// Represent each sector by its bounding box. Then for each pair of +// sectors, see if any chains of one-sided lines can walk from one +// side of the convex hull for that pair to the other side. +// +// It works, but it's far from being perfect. It's quite easy for +// this algorithm to consider two sectors as being visible from +// each other when they are really not. But it won't erroneously +// flag two sectors as obstructed when they're really not, and that's +// the only thing that really matters when building a REJECT lump. + +#include +#include + +#include "rejectbuilder.h" +#include "templates.h" + +FRejectBuilder::FRejectBuilder (FLevel &level) + : Level (level), BlockChains (NULL) +{ + RejectSize = (Level.NumSectors()*Level.NumSectors() + 7) / 8; + Reject = new BYTE[RejectSize]; + memset (Reject, 0, RejectSize); + + FindSectorBounds (); + FindBlockChains (); + BuildReject (); +} + +FRejectBuilder::~FRejectBuilder () +{ + FBlockChain *chain, *next; + + chain = BlockChains; + while (chain != NULL) + { + next = chain->Next; + delete chain; + chain = next; + } +} + +BYTE *FRejectBuilder::GetReject () +{ + return Reject; +} + +void FRejectBuilder::FindSectorBounds () +{ + int i; + + SectorBounds = new BBox[Level.NumSectors()]; + + for (i = 0; i < Level.NumSectors(); ++i) + { + SectorBounds[i].Bounds[LEFT] = SectorBounds[i].Bounds[BOTTOM] = INT_MAX; + SectorBounds[i].Bounds[RIGHT] = SectorBounds[i].Bounds[TOP] = INT_MIN; + } + + for (i = 0; i < Level.NumLines(); ++i) + { + if (Level.Lines[i].sidenum[0] != NO_INDEX) + { + int secnum = Level.Sides[Level.Lines[i].sidenum[0]].sector; + SectorBounds[secnum].AddPt (Level.Vertices[Level.Lines[i].v1]); + SectorBounds[secnum].AddPt (Level.Vertices[Level.Lines[i].v2]); + } + if (Level.Lines[i].sidenum[1] != NO_INDEX) + { + int secnum = Level.Sides[Level.Lines[i].sidenum[1]].sector; + SectorBounds[secnum].AddPt (Level.Vertices[Level.Lines[i].v1]); + SectorBounds[secnum].AddPt (Level.Vertices[Level.Lines[i].v2]); + } + } +} + +void FRejectBuilder::FindBlockChains () +{ + bool *marked = new bool[Level.NumLines()]; + WORD *nextForVert = new WORD[Level.NumLines()]; + WORD *firstLine = new WORD[Level.NumVertices]; + int i, j, k; + FBlockChain *chain; + FPoint pt; + TArray pts; + + memset (nextForVert, 0xff, Level.NumLines()*sizeof(*nextForVert)); + memset (firstLine, 0xff, Level.NumVertices*sizeof(*firstLine)); + memset (marked, 0, Level.NumLines()*sizeof(*marked)); + + for (i = 0; i < Level.NumLines(); ++i) + { + if (Level.Lines[i].sidenum[0] == NO_INDEX || Level.Lines[i].sidenum[1] != NO_INDEX) + { + marked[i] = true; + } + else + { + nextForVert[Level.Lines[i].v1] = firstLine[Level.Lines[i].v1]; + firstLine[Level.Lines[i].v1] = i; + } + } + + for (i = 0; i < Level.NumLines(); ++i) + { + if (marked[i]) + { + continue; + } + + pt.x = Level.Vertices[Level.Lines[i].v1].x >> FRACBITS; + pt.y = Level.Vertices[Level.Lines[i].v1].y >> FRACBITS; + pts.Clear (); + pts.Push (pt); + chain = new FBlockChain; + chain->Bounds(LEFT) = chain->Bounds(RIGHT) = pt.x; + chain->Bounds(TOP) = chain->Bounds(BOTTOM) = pt.y; + + for (j = i; j != NO_INDEX; ) + { + marked[j] = true; + pt.x = Level.Vertices[Level.Lines[j].v2].x >> FRACBITS; + pt.y = Level.Vertices[Level.Lines[j].v2].y >> FRACBITS; + pts.Push (pt); + chain->Bounds.AddPt (pt); + + k = firstLine[Level.Lines[j].v2]; + if (k == NO_INDEX) + { + break; + } + if (nextForVert[k] == NO_INDEX) + { + j = marked[k] ? NO_INDEX : k; + } + else + { + int best = NO_INDEX; + angle_t bestang = ANGLE_MAX; + angle_t ang1 = PointToAngle (Level.Vertices[Level.Lines[j].v2].x - Level.Vertices[Level.Lines[j].v1].x, + Level.Vertices[Level.Lines[j].v2].y - Level.Vertices[Level.Lines[j].v1].y) + (1 << 31); + + while (k != NO_INDEX) + { + if (!marked[k]) + { + angle_t ang2 = PointToAngle (Level.Vertices[Level.Lines[k].v2].x - Level.Vertices[Level.Lines[k].v1].x, + Level.Vertices[Level.Lines[k].v2].y - Level.Vertices[Level.Lines[k].v1].y) + (1 << 31); + angle_t angdiff = ang2 - ang1; + + if (angdiff < bestang && angdiff > 0) + { + bestang = angdiff; + best = k; + } + } + k = nextForVert[k]; + } + + j = best; + } + } + + chain->NumPoints = pts.Size(); + chain->Points = new FPoint[chain->NumPoints]; + memcpy (chain->Points, &pts[0], chain->NumPoints*sizeof(*chain->Points)); + chain->Next = BlockChains; + BlockChains = chain; + } +} + +void FRejectBuilder::HullSides (const BBox &box1, const BBox &box2, FPoint sides[4]) +{ + static const int vertSides[4][2] = { + { LEFT, BOTTOM }, + { LEFT, TOP }, + { RIGHT, TOP }, + { RIGHT, BOTTOM } + }; + static const int stuffSpots[4] = { 0, 3, 2, 1 }; + + const int *boxp1, *boxp2; + + boxp1 = box2.Bounds; + boxp2 = box1.Bounds; + + for (int mainBox = 2; mainBox != 0; ) + { + const int *stuffs = stuffSpots + (--mainBox)*2; + int outerEdges[4]; + + outerEdges[LEFT] = boxp1[LEFT] <= boxp2[LEFT]; + outerEdges[TOP] = boxp1[TOP] >= boxp2[TOP]; + outerEdges[RIGHT] = boxp1[RIGHT] >= boxp2[RIGHT]; + outerEdges[BOTTOM] = boxp1[BOTTOM] <= boxp2[BOTTOM]; + + for (int vertex = 0; vertex < 4; ++vertex) + { + if (outerEdges[(vertex-1)&3] != outerEdges[vertex]) + { + FPoint *pt = &sides[stuffs[outerEdges[vertex]]]; + pt->x = boxp1[vertSides[vertex][0]]; + pt->y = boxp1[vertSides[vertex][1]]; + } + } + + boxp1 = box1.Bounds; + boxp2 = box2.Bounds; + } +} + +int FRejectBuilder::PointOnSide (const FPoint *pt, const FPoint &lpt1, const FPoint &lpt2) +{ + return (pt->y - lpt1.y) * (lpt2.x - lpt1.x) >= (pt->x - lpt1.x) * (lpt2.y - lpt1.y); +} + +bool FRejectBuilder::ChainBlocks (const FBlockChain *chain, + const BBox *hullBounds, const FPoint *hullPts) +{ + int startSide, side, i; + + if (chain->Bounds[LEFT] > hullBounds->Bounds[RIGHT] || + chain->Bounds[RIGHT] < hullBounds->Bounds[LEFT] || + chain->Bounds[TOP] < hullBounds->Bounds[BOTTOM] || + chain->Bounds[BOTTOM] > hullBounds->Bounds[TOP]) + { + return false; + } + + startSide = -1; + + for (i = 0; i < chain->NumPoints; ++i) + { + const FPoint *pt = &chain->Points[i]; + + if (PointOnSide (pt, hullPts[1], hullPts[2])) + { + startSide = -1; + continue; + } + if (PointOnSide (pt, hullPts[3], hullPts[0])) + { + startSide = -1; + continue; + } + if (PointOnSide (pt, hullPts[0], hullPts[1])) + { + side = 0; + } + else if (PointOnSide (pt, hullPts[2], hullPts[3])) + { + side = 1; + } + else + { + continue; + } + if (startSide == -1 || startSide == side) + { + startSide = side; + } + else + { + return true; + } + } + + return false; +} + +void FRejectBuilder::BuildReject () +{ + int s1, s2; + + for (s1 = 0; s1 < Level.NumSectors()-1; ++s1) + { + printf (" Reject: %3d%%\r", s1*100/Level.NumSectors()); + for (s2 = s1 + 1; s2 < Level.NumSectors(); ++s2) + { + BBox HullBounds; + FPoint HullPts[4]; + const BBox *sb1, *sb2; + + sb1 = &SectorBounds[s1]; + sb2 = &SectorBounds[s2]; + + int pos = s1*Level.NumSectors() + s2; + if (Reject[pos>>3] & (1<<(pos&7))) + { + continue; + } + + // Overlapping and touching sectors are considered to always + // see each other. + if (sb1->Bounds[LEFT] <= sb2->Bounds[RIGHT] && + sb1->Bounds[RIGHT] >= sb2->Bounds[LEFT] && + sb1->Bounds[TOP] >= sb2->Bounds[BOTTOM] && + sb1->Bounds[BOTTOM] <= sb2->Bounds[TOP]) + { + continue; + } + + HullBounds(LEFT) = MIN (sb1->Bounds[LEFT], sb2->Bounds[LEFT]); + HullBounds(RIGHT) = MAX (sb1->Bounds[RIGHT], sb2->Bounds[RIGHT]); + HullBounds(BOTTOM) = MIN (sb1->Bounds[BOTTOM], sb2->Bounds[BOTTOM]); + HullBounds(TOP) = MAX (sb1->Bounds[TOP], sb2->Bounds[TOP]); + + HullSides (*sb1, *sb2, HullPts); + + for (FBlockChain *chain = BlockChains; chain != NULL; chain = chain->Next) + { + if (ChainBlocks (chain, &HullBounds, HullPts)) + { + break; + } + if (chain == NULL) + { + goto cont; + } + } + + cont: continue; + + Reject[pos>>3] |= 1 << (pos & 7); + pos = s2*Level.NumSectors() + s1; + Reject[pos>>3] |= 1 << (pos & 7); + } + } + printf (" Reject: 100%%\n"); +} diff --git a/zdbsp_src/Unused/rejectbuilder.cc.bak b/zdbsp_src/Unused/rejectbuilder.cc.bak new file mode 100644 index 0000000000..e18e85e47d --- /dev/null +++ b/zdbsp_src/Unused/rejectbuilder.cc.bak @@ -0,0 +1,278 @@ +// This is the same algorithm used by DoomBSP: +// +// Represent each sector by its bounding box. Then for each pair of +// sectors, see if any chains of one-sided lines can walk from one +// side of the convex hull for that pair to the other side. +// +// It works, but it's far from being perfect. It's quite easy for +// this algorithm to consider two sectors as being visible from +// each other when they are really not. But it won't erroneously +// flag two sectors as obstructed when they're really not, and that's +// the only thing that really matters when building a REJECT lump. + +#include +#include + +#include "zdbsp.h" +#include "nodebuild.h" +#include "rejectbuilder.h" +#include "templates.h" + +FRejectBuilder::FRejectBuilder (FLevel &level) + : Level (level) +{ + int i; + + i = Level.NumGLSubsectors * Level.NumGLSubsectors; + SubSeeMatrix = new BYTE[i]; + memset (SubSeeMatrix, 0, i); + + SegSubsectors = new WORD[Level.NumGLSegs]; + for (i = 0; i < Level.NumGLSubsectors; ++i) + { + for (int j = 0; j < Level.GLSubsectors[i].numlines; ++j) + { + SegSubsectors[j + Level.GLSubsectors[i].firstline] = i; + } + } + + BuildReject (); +} + +FRejectBuilder::~FRejectBuilder () +{ + delete[] SubSeeMatrix; + delete[] SegSubsectors; +} + +BYTE *FRejectBuilder::GetReject () +{ + int i, j; + + int rejectSize = (Level.NumSectors()*Level.NumSectors() + 7) / 8; + BYTE *reject = new BYTE[rejectSize]; + memset (reject, 0xff, rejectSize); + + int pvs_size = (Level.NumGLSubsectors * Level.NumGLSubsectors) + 7 / 8; + Level.GLPVS = new BYTE[pvs_size]; + Level.GLPVSSize = pvs_size; + memset (Level.GLPVS, 0, pvs_size); + + for (i = 0; i < Level.NumGLSubsectors; ++i) + { + int row = i*Level.NumGLSubsectors; + int sector1 = + Level.Sides[ + Level.Lines[ + Level.GLSegs[ + Level.GLSubsectors[i].firstline].linedef] + .sidenum[ + Level.GLSegs[ + Level.GLSubsectors[i].firstline].side]] + .sector; + int srow = sector1*Level.NumSectors(); + for (j = 0; j < Level.NumGLSubsectors; ++j) + { + if (SubSeeMatrix[row + j]) + { + int sector2 = + Level.Sides[ + Level.Lines[ + Level.GLSegs[ + Level.GLSubsectors[j].firstline].linedef] + .sidenum[ + Level.GLSegs[ + Level.GLSubsectors[j].firstline].side]] + .sector; + int l = (srow + sector2) >> 3; + int r = (srow + sector2) & 7; + reject[l] &= ~(1 << r); + + l = (row + j) >> 3; + r = (row + j) & 7; + Level.GLPVS[l] |= 1 << r; + } + } + } + + return reject; +} + +void FRejectBuilder::BuildReject () +{ + int s1, s2; + + for (s1 = 0; s1 < Level.NumGLSubsectors; ++s1) + { + //printf (" Reject: %3d%%\r",s1*100/Level.NumGLSubsectors); + printf ("%d/%d\r", s1, Level.NumGLSubsectors); + + // A subsector can always see itself + SourceRow = s1 * Level.NumGLSubsectors; + SubSeeMatrix[SourceRow + s1] = 1; + + WORD pusher = s1; + + for (s2 = 0; s2 < Level.GLSubsectors[s1].numlines; ++s2) + { + int segnum = s2 + Level.GLSubsectors[s1].firstline; + const MapSegGL *seg = (const MapSegGL*)&Level.GLSegs[segnum]; + if (seg->partner == NO_INDEX) + { + continue; + } + + SourceSeg = segnum; + SegRow = segnum * Level.NumGLSegs; + + TracePath (s1, seg); + } + } + printf (" Reject: 100%%\n"); +} + +inline const WideVertex *FRejectBuilder::GetVertex (WORD vertnum) +{ + if (vertnum & 0x8000) + { + return &Level.GLVertices[vertnum & 0x7fff]; + } + else + { + return &Level.Vertices[vertnum]; + } +} + +void FRejectBuilder::TracePath (int subsector, const MapSegGL *window) +{ + // Neighboring subsectors can always see each other + SubSeeMatrix[SourceRow + SegSubsectors[window->partner]] = 1; + const MapSubsector *backsub = (const MapSubsector*)&Level.GLSubsectors[SegSubsectors[window->partner]]; + + Portal source; + + source.Subsector = backsub; + source.Left = GetVertex (window->v1); + source.Right = GetVertex (window->v2); + + PortalStack.Clear (); + PortalStack.Push (source); + + fixed_t wdx = source.Right->x - source.Left->x; + fixed_t wdy = source.Right->y - source.Left->y; + +// printf ("start window %d\n", window - Level.GLSegs); + + for (int i = 0; i < backsub->numlines; ++i) + { + int segnum = backsub->firstline + i; + + if (segnum == window->partner) + { + continue; + } + + const MapSegGL *cseg = (const MapSegGL*)&Level.GLSegs[segnum]; + + if (cseg->partner == NO_INDEX) + { + continue; + } + + const WideVertex *cv1 = GetVertex (cseg->v1); + const WideVertex *cv2 = GetVertex (cseg->v2); + + if (FNodeBuilder::PointOnSide (cv1->x, cv1->y, source.Left->x, source.Left->y, wdx, wdy) == 0 && + FNodeBuilder::PointOnSide (cv2->x, cv2->y, source.Left->x, source.Left->y, wdx, wdy) == 0) + { + continue; + } + + TracePathDeep (cseg); + } +} + +void FRejectBuilder::TracePathDeep (const MapSegGL *window) +{ + SubSeeMatrix[SourceRow + SegSubsectors[window->partner]] = 1; + const MapSubsector *backsub = (const MapSubsector*)&Level.GLSubsectors[SegSubsectors[window->partner]]; + size_t j; + + for (j = PortalStack.Size(); j-- > 0; ) + { + if (PortalStack[j].Subsector == backsub) + { + return; + } + } + + Portal entrance; + + entrance.Subsector = backsub; + entrance.Left = GetVertex (window->v1); + entrance.Right = GetVertex (window->v2); + PortalStack.Push (entrance); + + fixed_t wdx = entrance.Right->x - entrance.Left->x; + fixed_t wdy = entrance.Right->y - entrance.Left->y; + + //printf ("deep through %d\n", window - Level.GLSegs); + + for (int i = 0; i < backsub->numlines; ++i) + { + int segnum = backsub->firstline + i; + + if (segnum == window->partner) + { + continue; + } + + const MapSegGL *cseg = (const MapSegGL*)&Level.GLSegs[segnum]; + + if (cseg->partner == NO_INDEX) + { + continue; + } + + const WideVertex *cv1 = GetVertex (cseg->v1); + const WideVertex *cv2 = GetVertex (cseg->v2); + + if (FNodeBuilder::PointOnSide (cv1->x, cv1->y, entrance.Left->x, entrance.Left->y, wdx, wdy) <= 0 && + FNodeBuilder::PointOnSide (cv2->x, cv2->y, entrance.Left->x, entrance.Left->y, wdx, wdy) <= 0) + { + continue; + } + + fixed_t leftx = PortalStack[0].Left->x; + fixed_t lefty = PortalStack[0].Left->y; + fixed_t rightx = PortalStack[0].Right->x; + fixed_t righty = PortalStack[0].Right->y; + + fixed_t leftdx = cv1->x - leftx; + fixed_t leftdy = cv1->y - lefty; + fixed_t rightdx = rightx - cv2->x; + fixed_t rightdy = righty - cv2->y; + + if (FNodeBuilder::PointOnSide (cv1->x, cv1->y, rightx, righty, rightdx, rightdy) >= 0 || + FNodeBuilder::PointOnSide (cv2->x, cv2->y, leftx, lefty, leftdx, leftdy) >= 0) + { + continue; + } + + for (j = PortalStack.Size(); j-- > 1; ) + { + if (FNodeBuilder::PointOnSide (PortalStack[j].Left->x, PortalStack[j].Left->y, + rightx, righty, rightdx, rightdy) >= 0 || + FNodeBuilder::PointOnSide (PortalStack[j].Right->x, PortalStack[j].Right->y, + leftx, lefty, leftdx, leftdy) >= 0) + { + break; + } + } + if (j == 0) + { + TracePathDeep (cseg); + } + } + PortalStack.Pop (entrance); +} diff --git a/zdbsp_src/Unused/rejectbuilder.h.bak b/zdbsp_src/Unused/rejectbuilder.h.bak new file mode 100644 index 0000000000..2375ef1293 --- /dev/null +++ b/zdbsp_src/Unused/rejectbuilder.h.bak @@ -0,0 +1,40 @@ +#include "zdbsp.h" +#include "tarray.h" +#include "doomdata.h" + +class FRejectBuilder +{ +public: + FRejectBuilder (FLevel &level); + ~FRejectBuilder (); + + BYTE *GetReject (); + +private: + struct Portal + { + const MapSubsector *Subsector; + const WideVertex *Left; + const WideVertex *Right; + }; + enum ESegSeeStatus + { + MIGHT_SEE, + CAN_SEE, + CANNOT_SEE + }; + + void BuildReject (); + void TracePath (int subsector, const MapSegGL *window); + void TracePathDeep (const MapSegGL *window); + inline const WideVertex *GetVertex (WORD vertnum); + + BYTE *SubSeeMatrix; + WORD *SegSubsectors; + TArray PortalStack; + + FLevel &Level; + + int SourceRow; + int SourceSeg, SegRow; +}; diff --git a/zdbsp_src/rejectbuilder.cc b/zdbsp_src/rejectbuilder.cc index e18e85e47d..f4a968a5b3 100644 --- a/zdbsp_src/rejectbuilder.cc +++ b/zdbsp_src/rejectbuilder.cc @@ -1,14 +1,20 @@ -// This is the same algorithm used by DoomBSP: +// The old code used the same algorithm as DoomBSP: // -// Represent each sector by its bounding box. Then for each pair of -// sectors, see if any chains of one-sided lines can walk from one -// side of the convex hull for that pair to the other side. +// Represent each sector by its bounding box. Then for each pair of +// sectors, see if any chains of one-sided lines can walk from one +// side of the convex hull for that pair to the other side. // -// It works, but it's far from being perfect. It's quite easy for -// this algorithm to consider two sectors as being visible from -// each other when they are really not. But it won't erroneously -// flag two sectors as obstructed when they're really not, and that's -// the only thing that really matters when building a REJECT lump. +// It works, but it's far from being perfect. It's quite easy for +// this algorithm to consider two sectors as being visible from +// each other when they are really not. But it won't erroneously +// flag two sectors as obstructed when they're really not, and that's +// the only thing that really matters when building a REJECT lump. +// +// Because that was next to useless, I scrapped that code and adapted +// Quake's vis utility to work in a 2D world. Since this is basically vis, +// it depends on GL nodes being present to function. As the usefulness +// of a REJECT lump is debatable, I have chosen to not compile this module +// in with ZDBSP. Save yourself some space and run ZDBSP with the -r option. #include #include @@ -18,35 +24,32 @@ #include "rejectbuilder.h" #include "templates.h" +bool MergeVis=1; + FRejectBuilder::FRejectBuilder (FLevel &level) - : Level (level) + : Level (level), testlevel (2), totalvis (0) { - int i; - - i = Level.NumGLSubsectors * Level.NumGLSubsectors; - SubSeeMatrix = new BYTE[i]; - memset (SubSeeMatrix, 0, i); + LoadPortals (); - SegSubsectors = new WORD[Level.NumGLSegs]; - for (i = 0; i < Level.NumGLSubsectors; ++i) + if (MergeVis) { - for (int j = 0; j < Level.GLSubsectors[i].numlines; ++j) - { - SegSubsectors[j + Level.GLSubsectors[i].firstline] = i; - } + MergeLeaves (); + MergeLeafPortals (); } + CountActivePortals (); + CalcVis (); + BuildReject (); } FRejectBuilder::~FRejectBuilder () { - delete[] SubSeeMatrix; - delete[] SegSubsectors; } BYTE *FRejectBuilder::GetReject () { + WORD *sectormap; int i, j; int rejectSize = (Level.NumSectors()*Level.NumSectors() + 7) / 8; @@ -58,39 +61,27 @@ BYTE *FRejectBuilder::GetReject () Level.GLPVSSize = pvs_size; memset (Level.GLPVS, 0, pvs_size); + sectormap = new WORD[Level.NumGLSubsectors]; for (i = 0; i < Level.NumGLSubsectors; ++i) { - int row = i*Level.NumGLSubsectors; - int sector1 = - Level.Sides[ - Level.Lines[ - Level.GLSegs[ - Level.GLSubsectors[i].firstline].linedef] - .sidenum[ - Level.GLSegs[ - Level.GLSubsectors[i].firstline].side]] - .sector; - int srow = sector1*Level.NumSectors(); + const MapSegGLEx *seg = &Level.GLSegs[Level.GLSubsectors[i].firstline]; + sectormap[i] = Level.Sides[Level.Lines[seg->linedef].sidenum[seg->side]].sector; + } + + for (i = 0; i < Level.NumGLSubsectors; ++i) + { + int rowpvs = i*Level.NumGLSubsectors; + int rowrej = sectormap[i]*Level.NumSectors(); + BYTE *bytes = visBytes + i*leafbytes; for (j = 0; j < Level.NumGLSubsectors; ++j) { - if (SubSeeMatrix[row + j]) + if (bytes[j>>3] & (1<<(j&7))) { - int sector2 = - Level.Sides[ - Level.Lines[ - Level.GLSegs[ - Level.GLSubsectors[j].firstline].linedef] - .sidenum[ - Level.GLSegs[ - Level.GLSubsectors[j].firstline].side]] - .sector; - int l = (srow + sector2) >> 3; - int r = (srow + sector2) & 7; - reject[l] &= ~(1 << r); - - l = (row + j) >> 3; - r = (row + j) & 7; - Level.GLPVS[l] |= 1 << r; + int mark = rowpvs + j; + Level.GLPVS[mark>>3] |= 1<<(mark&7); + + mark = rowrej + sectormap[j]; + reject[mark>>3] &= ~(1<<(mark&7)); } } } @@ -100,179 +91,126 @@ BYTE *FRejectBuilder::GetReject () void FRejectBuilder::BuildReject () { - int s1, s2; - - for (s1 = 0; s1 < Level.NumGLSubsectors; ++s1) - { - //printf (" Reject: %3d%%\r",s1*100/Level.NumGLSubsectors); - printf ("%d/%d\r", s1, Level.NumGLSubsectors); - - // A subsector can always see itself - SourceRow = s1 * Level.NumGLSubsectors; - SubSeeMatrix[SourceRow + s1] = 1; - - WORD pusher = s1; - - for (s2 = 0; s2 < Level.GLSubsectors[s1].numlines; ++s2) - { - int segnum = s2 + Level.GLSubsectors[s1].firstline; - const MapSegGL *seg = (const MapSegGL*)&Level.GLSegs[segnum]; - if (seg->partner == NO_INDEX) - { - continue; - } - - SourceSeg = segnum; - SegRow = segnum * Level.NumGLSegs; - - TracePath (s1, seg); - } - } - printf (" Reject: 100%%\n"); } inline const WideVertex *FRejectBuilder::GetVertex (WORD vertnum) { - if (vertnum & 0x8000) - { - return &Level.GLVertices[vertnum & 0x7fff]; - } - else - { - return &Level.Vertices[vertnum]; - } + return &Level.Vertices[vertnum]; } -void FRejectBuilder::TracePath (int subsector, const MapSegGL *window) +FRejectBuilder::FLeaf::FLeaf () + : numportals (0), merged (-1), portals (NULL) { - // Neighboring subsectors can always see each other - SubSeeMatrix[SourceRow + SegSubsectors[window->partner]] = 1; - const MapSubsector *backsub = (const MapSubsector*)&Level.GLSubsectors[SegSubsectors[window->partner]]; - - Portal source; - - source.Subsector = backsub; - source.Left = GetVertex (window->v1); - source.Right = GetVertex (window->v2); - - PortalStack.Clear (); - PortalStack.Push (source); - - fixed_t wdx = source.Right->x - source.Left->x; - fixed_t wdy = source.Right->y - source.Left->y; - -// printf ("start window %d\n", window - Level.GLSegs); +} - for (int i = 0; i < backsub->numlines; ++i) +FRejectBuilder::FLeaf::~FLeaf () +{ + if (portals != NULL) { - int segnum = backsub->firstline + i; - - if (segnum == window->partner) - { - continue; - } - - const MapSegGL *cseg = (const MapSegGL*)&Level.GLSegs[segnum]; - - if (cseg->partner == NO_INDEX) - { - continue; - } - - const WideVertex *cv1 = GetVertex (cseg->v1); - const WideVertex *cv2 = GetVertex (cseg->v2); - - if (FNodeBuilder::PointOnSide (cv1->x, cv1->y, source.Left->x, source.Left->y, wdx, wdy) == 0 && - FNodeBuilder::PointOnSide (cv2->x, cv2->y, source.Left->x, source.Left->y, wdx, wdy) == 0) - { - continue; - } - - TracePathDeep (cseg); + delete[] portals; } } -void FRejectBuilder::TracePathDeep (const MapSegGL *window) +int FRejectBuilder::PointOnSide (const FPoint &point, const FLine &line) { - SubSeeMatrix[SourceRow + SegSubsectors[window->partner]] = 1; - const MapSubsector *backsub = (const MapSubsector*)&Level.GLSubsectors[SegSubsectors[window->partner]]; - size_t j; + return FNodeBuilder::PointOnSide (point.x, point.y, line.x, line.y, line.dx, line.dy); +} + +void FRejectBuilder::LoadPortals () +{ + WORD *segleaf; + int i, j, k, max; + VPortal *p; + FLeaf *l; + FWinding *w; + + printf("GL SUBS: %d\n", Level.NumGLSubsectors); + portalclusters = Level.NumGLSubsectors; - for (j = PortalStack.Size(); j-- > 0; ) + for (numportals = 0, i = 0; i < Level.NumGLSegs; ++i) { - if (PortalStack[j].Subsector == backsub) + if (Level.GLSegs[i].partner != DWORD_MAX) { - return; + ++numportals; } } - Portal entrance; + // these counts should take advantage of 64 bit systems automatically + leafbytes = ((portalclusters+63)&~63)>>3; + leaflongs = leafbytes/sizeof(long); - entrance.Subsector = backsub; - entrance.Left = GetVertex (window->v1); - entrance.Right = GetVertex (window->v2); - PortalStack.Push (entrance); + portalbytes = ((numportals+63)&~63)>>3; + portallongs = portalbytes/sizeof(long); - fixed_t wdx = entrance.Right->x - entrance.Left->x; - fixed_t wdy = entrance.Right->y - entrance.Left->y; + portals = new VPortal[numportals]; + memset (portals, 0, numportals*sizeof(VPortal)); + + leafs = new FLeaf[portalclusters]; - //printf ("deep through %d\n", window - Level.GLSegs); + numVisBytes = portalclusters*leafbytes; + visBytes = new BYTE[numVisBytes]; - for (int i = 0; i < backsub->numlines; ++i) + segleaf = new WORD[Level.NumGLSegs]; + for (i = 0; i < Level.NumGLSubsectors; ++i) { - int segnum = backsub->firstline + i; + j = Level.GLSubsectors[i].firstline; + max = j + Level.GLSubsectors[i].numlines; - if (segnum == window->partner) + for (; j < max; ++j) { - continue; + segleaf[j] = i; } + } - const MapSegGL *cseg = (const MapSegGL*)&Level.GLSegs[segnum]; + p = portals; + l = leafs; + for (i = 0; i < Level.NumGLSubsectors; ++i, ++l) + { + j = Level.GLSubsectors[i].firstline; + max = j + Level.GLSubsectors[i].numlines; - if (cseg->partner == NO_INDEX) + // Count portals in this leaf + for (; j < max; ++j) { - continue; + if (Level.GLSegs[j].partner != DWORD_MAX) + { + ++l->numportals; + } } - const WideVertex *cv1 = GetVertex (cseg->v1); - const WideVertex *cv2 = GetVertex (cseg->v2); - - if (FNodeBuilder::PointOnSide (cv1->x, cv1->y, entrance.Left->x, entrance.Left->y, wdx, wdy) <= 0 && - FNodeBuilder::PointOnSide (cv2->x, cv2->y, entrance.Left->x, entrance.Left->y, wdx, wdy) <= 0) + if (l->numportals == 0) { continue; } - fixed_t leftx = PortalStack[0].Left->x; - fixed_t lefty = PortalStack[0].Left->y; - fixed_t rightx = PortalStack[0].Right->x; - fixed_t righty = PortalStack[0].Right->y; + l->portals = new VPortal *[l->numportals]; - fixed_t leftdx = cv1->x - leftx; - fixed_t leftdy = cv1->y - lefty; - fixed_t rightdx = rightx - cv2->x; - fixed_t rightdy = righty - cv2->y; - - if (FNodeBuilder::PointOnSide (cv1->x, cv1->y, rightx, righty, rightdx, rightdy) >= 0 || - FNodeBuilder::PointOnSide (cv2->x, cv2->y, leftx, lefty, leftdx, leftdy) >= 0) + for (k = 0, j = Level.GLSubsectors[i].firstline; j < max; ++j) { - continue; - } + const MapSegGLEx *seg = &Level.GLSegs[j]; - for (j = PortalStack.Size(); j-- > 1; ) - { - if (FNodeBuilder::PointOnSide (PortalStack[j].Left->x, PortalStack[j].Left->y, - rightx, righty, rightdx, rightdy) >= 0 || - FNodeBuilder::PointOnSide (PortalStack[j].Right->x, PortalStack[j].Right->y, - leftx, lefty, leftdx, leftdy) >= 0) + if (seg->partner == DWORD_MAX) { - break; + continue; } - } - if (j == 0) - { - TracePathDeep (cseg); + + // create portal from seg + l->portals[k++] = p; + + w = &p->winding; + w->points[0] = GetVertex (seg->v1); + w->points[1] = GetVertex (seg->v2); + + p->hint = seg->linedef != NO_INDEX; + p->line.x = w->points[1].x; + p->line.y = w->points[1].y; + p->line.dx = w->points[0].x - p->line.x; + p->line.dy = w->points[0].y - p->line.y; + p->leaf = segleaf[seg->partner]; + + p++; } } - PortalStack.Pop (entrance); + + delete[] segleaf; } diff --git a/zdbsp_src/rejectbuilder.h b/zdbsp_src/rejectbuilder.h index 2375ef1293..bcec125cdc 100644 --- a/zdbsp_src/rejectbuilder.h +++ b/zdbsp_src/rejectbuilder.h @@ -1,3 +1,5 @@ +// A slight adaptation of Quake's vis utility. + #include "zdbsp.h" #include "tarray.h" #include "doomdata.h" @@ -11,30 +13,199 @@ class FRejectBuilder BYTE *GetReject (); private: - struct Portal + void BuildReject (); + void LoadPortals (); + inline const WideVertex *GetVertex (WORD vertnum); + FLevel &Level; + +/* Looky! Stuff from vis.h: */ + +// seperator caching helps a bit +//#define SEPERATORCACHE + +// can't have more seperators than the max number of points on a winding + static const int MAX_SEPERATORS = 2; + static const int MAX_PORTALS = 65536*2/3; + static const int MAX_MAP_LEAFS = 32768; + static const int MAX_PORTALS_ON_LEAF = MAX_PORTALS; + + struct FPoint { - const MapSubsector *Subsector; - const WideVertex *Left; - const WideVertex *Right; + fixed_t x, y; + + const FPoint &operator= (const WideVertex *other) + { + x = other->x; + y = other->y; + return *this; + } }; - enum ESegSeeStatus + + struct FLine : public FPoint { - MIGHT_SEE, - CAN_SEE, - CANNOT_SEE + fixed_t dx, dy; + + void Flip () + { + x += dx; + y += dy; + dx = -dx; + dy = -dy; + } }; - void BuildReject (); - void TracePath (int subsector, const MapSegGL *window); - void TracePathDeep (const MapSegGL *window); - inline const WideVertex *GetVertex (WORD vertnum); + struct FWinding + { + FPoint points[2]; + }; - BYTE *SubSeeMatrix; - WORD *SegSubsectors; - TArray PortalStack; + struct FPassage + { + FPassage *next; + BYTE cansee[1]; //all portals that can be seen through this passage + }; - FLevel &Level; + enum VStatus + { + STAT_None, + STAT_Working, + STAT_Done + }; + + struct VPortal + { + bool removed; + bool hint; + FLine line; // neighbor is on front/right side + int leaf; // neighbor + + FWinding winding; + VStatus status; + BYTE *portalfront; // [portals], preliminary + BYTE *portalflood; // [portals], intermediate + BYTE *portalvis; // [portals], final + + int nummightsee; // bit count on portalflood for sort + FPassage *passages; // there are just as many passages as there + // are portals in the leaf this portal leads to + }; + + struct FLeaf + { + FLeaf (); + ~FLeaf (); + + int numportals; + int merged; + VPortal **portals; + }; + + struct PStack + { + BYTE mightsee[MAX_PORTALS/8]; // bit string + PStack *next; + FLeaf *leaf; + VPortal *portal; // portal exiting + FWinding *source; + FWinding *pass; + + FWinding windings[3]; // source, pass, temp in any order + bool freewindings[3]; + + FLine portalline; + int depth; + #ifdef SEPERATORCACHE + FLine seperators[2][MAX_SEPERATORS]; + int numseperators[2]; + #endif + }; + + struct FThreadData + { + VPortal *base; + int c_chains; + PStack pstack_head; + }; + + int numportals; + int portalclusters; + + VPortal *portals; + FLeaf *leafs; + + int c_portaltest, c_portalpass, c_portalcheck; + int c_portalskip, c_leafskip; + int c_vistest, c_mighttest; + int c_chains; + + int testlevel; + + BYTE *uncompressed; + + int leafbytes, leaflongs; + int portalbytes, portallongs; + + int numVisBytes; + BYTE *visBytes; + + int totalvis; + + void LeafFlow (int leafnum); + + void BasePortalVis (int portalnum); + void BetterPortalVis (int portalnum); + void PortalFlow (int portalnum); + void PassagePortalFlow (int portalnum); + void CreatePassages (int portalnum); + void PassageFlow (int portalnum); + + VPortal *sorted_portals[65536]; + + static int CountBits (BYTE *bits, int numbits); + + void SortPortals (); + static int PComp (const void *a, const void *b); + int LeafVectorFromPortalVector (BYTE *portalbits, BYTE *leafbits); + void ClusterMerge (int leafnum); + + void PassageMemory (); + void CalcFastVis (); + void CalcVis (); + void CalcPortalVis (); + void CalcPassagePortalVis (); + + int CountActivePortals (); + + bool pacifier; + int workcount; + int dispatch; + int oldf; + int oldcount; + + void RunThreadsOnIndividual (int workcnt, bool showpacifire, void (FRejectBuilder::*func)(int)); + int GetThreadWork (); + + void CheckStack (FLeaf *leaf, FThreadData *thread); + + FWinding *AllocStackWinding (PStack *stack) const; + void FreeStackWinding (FWinding *w, PStack *stack) const; + + FWinding *VisChopWinding (FWinding *in, PStack *stack, FLine *split); + FWinding *ClipToSeperators (FWinding *source, FWinding *pass, FWinding *target, bool flipclip, PStack *stack); + void RecursiveLeafFlow (int leafnum, FThreadData *thread, PStack *prevstack); + void RecursivePassageFlow (VPortal *portal, FThreadData *thread, PStack *prevstack); + void RecursivePassagePortalFlow (VPortal *portal, FThreadData *thread, PStack *prevstack); + FWinding *PassageChopWinding (FWinding *in, FWinding *out, FLine *split); + int AddSeperators (FWinding *source, FWinding *pass, bool flipclip, FLine *seperators, int maxseperators); + void SimpleFlood (VPortal *srcportal, int leafnum); + void RecursiveLeafBitFlow (int leafnum, BYTE *mightsee, BYTE *cansee); + + int PointOnSide (const FPoint &point, const FLine &line); - int SourceRow; - int SourceSeg, SegRow; + bool TryMergeLeaves (int l1num, int l2num); + void UpdatePortals (); + void MergeLeaves (); + FWinding *TryMergeWinding (FWinding *f1, FWinding *f2, const FLine &line); + void MergeLeafPortals (); + bool Winding_PlanesConcave (const FWinding *w1, const FWinding *w2, const FLine &line1, const FLine &line2); }; diff --git a/zdbsp_src/vis.cc b/zdbsp_src/vis.cc new file mode 100644 index 0000000000..8294065522 --- /dev/null +++ b/zdbsp_src/vis.cc @@ -0,0 +1,588 @@ +// An adaptation of the Quake vis utility. + +#include +#include + +#include "zdbsp.h" +#include "nodebuild.h" +#include "rejectbuilder.h" +#include "templates.h" +#include + +bool FastVis=0; +bool NoPassageVis=1; +bool NoSort; + +//============================================================================= + +/* +============= +GetThreadWork + +============= +*/ +int FRejectBuilder::GetThreadWork () +{ + int r; + int f; + + if (dispatch == workcount) + { + return -1; + } + + if (pacifier) + { + if (dispatch >= workcount-1) + { + fprintf (stderr, "100%%\n"); + } + else if (oldcount < 0 || dispatch - oldcount > 200) + { + oldcount = dispatch; + f = 100*dispatch / workcount; + if (f != oldf) + { + oldf = f; + fprintf (stderr, "% 3d%%\b\b\b\b", f); + } + } + } + + r = dispatch++; + + return r; +} +void FRejectBuilder::RunThreadsOnIndividual (int workcnt, bool showpacifier, void(FRejectBuilder::*func)(int)) +{ + int work; + + pacifier = showpacifier; + workcount = workcnt; + dispatch = 0; + oldf = -1; + oldcount = -1; + + while (-1 != (work = GetThreadWork ())) + { + (this->*func) (work); + } +} + +//============================================================================= + +/* +============= +SortPortals + +Sorts the portals from the least complex, so the later ones can reuse +the earlier information. +============= +*/ +int FRejectBuilder::PComp (const void *a, const void *b) +{ + return (*(VPortal **)a)->nummightsee - (*(VPortal **)b)->nummightsee; +} +void FRejectBuilder::SortPortals () +{ + for (int i = 0; i < numportals; i++) + { + sorted_portals[i] = &portals[i]; + } + + if (!NoSort) + { + qsort (sorted_portals, numportals, sizeof(sorted_portals[0]), PComp); + } +} + + +/* +============== +LeafVectorFromPortalVector +============== +*/ +int FRejectBuilder::LeafVectorFromPortalVector (BYTE *portalbits, BYTE *leafbits) +{ + int i, j, leafnum; + VPortal *p; + int c_leafs; + + + for (i = 0; i < numportals; i++) + { + if (portalbits[i>>3] & (1<<(i&7)) ) + { + p = portals+i; + leafbits[p->leaf>>3] |= (1<<(p->leaf&7)); + //printf ("pleaf: from %d to %d\n", i, p->leaf); + } + } + + for (j = 0; j < portalclusters; j++) + { + leafnum = j; + while (leafs[leafnum].merged >= 0) + { + leafnum = leafs[leafnum].merged; + } + //if the merged leaf is visible then the original leaf is visible + if (leafbits[leafnum>>3] & (1<<(leafnum&7))) + { + leafbits[j>>3] |= (1<<(j&7)); + } + } + + c_leafs = CountBits (leafbits, portalclusters); + + return c_leafs; +} + + +/* +=============== +ClusterMerge + +Merges the portal visibility for a leaf +=============== +*/ +void FRejectBuilder::ClusterMerge (int leafnum) +{ + FLeaf *leaf; + BYTE portalvector[MAX_PORTALS/8]; + BYTE uncompressed[MAX_MAP_LEAFS/8]; + int i, j; + int numvis, mergedleafnum; + VPortal *p; + int pnum; + + // OR together all the portalvis bits + + mergedleafnum = leafnum; + while (leafs[mergedleafnum].merged >= 0) + { + mergedleafnum = leafs[mergedleafnum].merged; + } + + memset (portalvector, 0, portalbytes); + leaf = &leafs[mergedleafnum]; + for (i = 0; i < leaf->numportals; i++) + { + p = leaf->portals[i]; + if (p->removed) + continue; + try + { + if (p->status != STAT_Done) + { + throw std::runtime_error("portal not done"); + } + } + catch (std::runtime_error &msg) + { + printf ("%s\n", msg.what()); + } + for (j = 0; j < portallongs; j++) + { + ((long *)portalvector)[j] |= ((long *)p->portalvis)[j]; + } + pnum = p - portals; + portalvector[pnum>>3] |= 1<<(pnum&7); + } + + memset (uncompressed, 0, leafbytes); + + // convert portal bits to leaf bits + uncompressed[mergedleafnum>>3] |= (1<<(mergedleafnum&7)); + numvis = LeafVectorFromPortalVector (portalvector, uncompressed); + + totalvis += numvis; + + //printf ("cluster %4i : %4i visible\n", leafnum, numvis); + + memcpy (visBytes + leafnum*leafbytes, uncompressed, leafbytes); +} + + +/* +================== +CalcPortalVis +================== +*/ +void FRejectBuilder::CalcPortalVis () +{ +#ifdef MREDEBUG + _printf("%6d portals out of %d", 0, numportals); + //get rid of the counter + RunThreadsOnIndividual (numportals, false, &FRejectBuilder::PortalFlow); +#else + RunThreadsOnIndividual (numportals, true, &FRejectBuilder::PortalFlow); +#endif + +} + +/* +================== +CalcPassagePortalVis +================== +*/ + +void FRejectBuilder::CalcPassagePortalVis () +{ + PassageMemory(); +#ifdef MREDEBUG + _printf("%6d portals out of %d", 0, numportals); + RunThreadsOnIndividual (numportals, false, &FRejectBuilder::CreatePassages); + _printf("\n"); + _printf("%6d portals out of %d", 0, numportals); + RunThreadsOnIndividual (numportals, false, &FRejectBuilder::PassagePortalFlow); + _printf("\n"); +#else + RunThreadsOnIndividual (numportals, true, &FRejectBuilder::CreatePassages); + printf (" Vis 3: "); + RunThreadsOnIndividual (numportals, true, &FRejectBuilder::PassagePortalFlow); +#endif +} + +/* +================== +CalcFastVis +================== +*/ +void FRejectBuilder::CalcFastVis () +{ + // fastvis just uses mightsee for a very loose bound + for (int i = 0; i < numportals; i++) + { + portals[i].portalvis = portals[i].portalflood; + portals[i].status = STAT_Done; + } +} + +/* +================== +CalcVis +================== +*/ +void FRejectBuilder::CalcVis () +{ + printf (" Vis 1: "); + RunThreadsOnIndividual (numportals, true, &FRejectBuilder::BasePortalVis); + +// RunThreadsOnIndividual (numportals, true, BetterPortalVis); + + SortPortals (); + + printf (" Vis 2: "); + if (FastVis) + CalcFastVis(); + else if (NoPassageVis) + CalcPortalVis (); + else + CalcPassagePortalVis(); + // + // assemble the leaf vis lists by oring and compressing the portal lists + // + printf ("creating leaf vis...\n"); + printf ("PORTAL CLUSTERS: %d\n", portalclusters); + printf ("TOTAL VIS: %d\n", totalvis); + for (int i = 0; i < portalclusters; i++) + { + ClusterMerge (i); + } + + printf ("Average clusters visible: %i\n", totalvis / portalclusters); +} + +/* +============= +Winding_PlanesConcave +============= +*/ +bool FRejectBuilder::Winding_PlanesConcave (const FWinding *w1, const FWinding *w2, + const FLine &line1, const FLine &line2) +{ + // check if one of the points of winding 1 is at the front of the line of winding 2 + if (PointOnSide (w1->points[0], line2) < 0 || + PointOnSide (w1->points[1], line2) < 0) + { + return true; + } + + // check if one of the points of winding 2 is at the front of the line of winding 1 + if (PointOnSide (w2->points[0], line1) < 0 || + PointOnSide (w2->points[1], line1) < 0) + { + return true; + } + + return false; +} + +/* +============ +TryMergeLeaves +============ +*/ +bool FRejectBuilder::TryMergeLeaves (int l1num, int l2num) +{ + int i, j, numportals; + FLeaf *l1, *l2; + VPortal *p1, *p2; + VPortal *portals[MAX_PORTALS_ON_LEAF]; + + l1 = &leafs[l1num]; + for (i = 0; i < l1->numportals; i++) + { + p1 = l1->portals[i]; + if (p1->leaf == l2num) continue; + l2 = &leafs[l2num]; + for (j = 0; j < l2->numportals; j++) + { + p2 = l2->portals[j]; + if (p2->leaf == l1num) continue; + // + if (Winding_PlanesConcave (&p1->winding, &p2->winding, p1->line, p2->line)) + return false; + } + } + l1 = &leafs[l1num]; + l2 = &leafs[l2num]; + numportals = 0; + //the leaves can be merged now + for (i = 0; i < l1->numportals; i++) + { + p1 = l1->portals[i]; + if (p1->leaf == l2num) + { + p1->removed = true; + continue; + } + portals[numportals++] = p1; + } + for (j = 0; j < l2->numportals; j++) + { + p2 = l2->portals[j]; + if (p2->leaf == l1num) + { + p2->removed = true; + continue; + } + portals[numportals++] = p2; + } + delete[] l1->portals; + l1->portals = NULL; + l1->numportals = 0; + delete[] l2->portals; + l2->portals = new VPortal *[numportals]; + for (i = 0; i < numportals; i++) + { + l2->portals[i] = portals[i]; + } + l2->numportals = numportals; + l1->merged = l2num; + return true; +} + +/* +============ +UpdatePortals +============ +*/ +void FRejectBuilder::UpdatePortals () +{ + int i; + VPortal *p; + + for (i = 0; i < numportals; i++) + { + p = &portals[i]; + if (!p->removed) + { + while (leafs[p->leaf].merged >= 0) + { + p->leaf = leafs[p->leaf].merged; + } + } + } +} + +/* +============ +MergeLeaves + +try to merge leaves but don't merge through hint splitters +============ +*/ +void FRejectBuilder::MergeLeaves () +{ + int i, j, nummerges, totalnummerges; + FLeaf *leaf; + VPortal *p; + + totalnummerges = 0; + do + { + printf ("."); + nummerges = 0; + for (i = 0; i < portalclusters; i++) + { + leaf = &leafs[i]; + //if this leaf is merged already + if (leaf->merged >= 0) + continue; + // + for (j = 0; j < leaf->numportals; j++) + { + p = leaf->portals[j]; + // + if (p->removed) + continue; + //never merge through hint portals + if (p->hint) + continue; + if (TryMergeLeaves(i, p->leaf)) + { + UpdatePortals(); + nummerges++; + break; + } + } + } + totalnummerges += nummerges; + } while (nummerges); + printf("\r%6d leaves merged\n", totalnummerges); +} + +/* +============ +TryMergeWinding +============ +*/ + +FRejectBuilder::FWinding *FRejectBuilder::TryMergeWinding (FWinding *f1, FWinding *f2, const FLine &line) +{ + static FWinding result; + int i, j; + + // + // find a common point + // + for (i = 0; i < 2; ++i) + { + for (j = 0; j < 2; ++j) + { + if (f1->points[i].x == f2->points[j].x && + f1->points[i].y == f2->points[j].y) + { + goto found; + } + } + } + + // no shared point + return NULL; + +found: + // + // if the lines are colinear, the point can be removed + // + if (PointOnSide (f2->points[0], line) != 0 || + PointOnSide (f2->points[1], line) != 0) + { // not colinear + return NULL; + } + + // + // build the new segment + // + if (i == 0) + { + result.points[0] = f2->points[!j]; + result.points[1] = f1->points[1]; + } + else + { + result.points[0] = f1->points[0]; + result.points[1] = f2->points[!j]; + } + return &result; +} + +/* +============ +MergeLeafPortals +============ +*/ +void FRejectBuilder::MergeLeafPortals () +{ + int i, j, k, nummerges, hintsmerged; + FLeaf *leaf; + VPortal *p1, *p2; + FWinding *w; + + nummerges = 0; + hintsmerged = 0; + for (i = 0; i < portalclusters; i++) + { + leaf = &leafs[i]; + if (leaf->merged >= 0) continue; + for (j = 0; j < leaf->numportals; j++) + { + p1 = leaf->portals[j]; + if (p1->removed) + continue; + for (k = j+1; k < leaf->numportals; k++) + { + p2 = leaf->portals[k]; + if (p2->removed) + continue; + if (p1->leaf == p2->leaf) + { + w = TryMergeWinding (&p1->winding, &p2->winding, p1->line); + if (w) + { + p1->winding = *w; + if (p1->hint && p2->hint) + hintsmerged++; + p1->hint |= p2->hint; + p2->removed = true; + nummerges++; + i--; + break; + } + } + } + if (k < leaf->numportals) + break; + } + } + printf("%6d portals merged\n", nummerges); + printf("%6d hint portals merged\n", hintsmerged); +} + +/* +============ +CountActivePortals +============ +*/ +int FRejectBuilder::CountActivePortals () +{ + int num, hints, j; + VPortal *p; + + num = 0; + hints = 0; + for (j = 0; j < numportals; j++) + { + p = portals + j; + if (p->removed) + continue; + if (p->hint) + hints++; + num++; + } + printf("%6d of %d active portals\n", num, numportals); + printf("%6d hint portals\n", hints); + return num; +} diff --git a/zdbsp_src/visflow.cc b/zdbsp_src/visflow.cc new file mode 100644 index 0000000000..517086e0b6 --- /dev/null +++ b/zdbsp_src/visflow.cc @@ -0,0 +1,1373 @@ +// An adaptation of the Quake vis utility. + +#include +#include +#include + +#include "zdbsp.h" +#include "nodebuild.h" +#include "rejectbuilder.h" +#include "templates.h" + +enum { SIDE_FRONT, SIDE_BACK, SIDE_ON }; + +/* + + each portal will have a list of all possible to see from first portal + + if (!thread->portalmightsee[portalnum]) + + portal mightsee + + for p2 = all other portals in leaf + get sperating planes + for all portals that might be seen by p2 + mark as unseen if not present in seperating plane + flood fill a new mightsee + save as passagemightsee + + + void CalcMightSee (leaf_t *leaf, +*/ + +int FRejectBuilder::CountBits (BYTE *bits, int numbits) +{ + int i; + int c; + + c = 0; + for (i=0 ; i>3] & (1<<(i&7)) ) + c++; + + return c; +} + +int c_fullskip; +int c_portalskip, c_leafskip; +int c_vistest, c_mighttest; + +int c_chop, c_nochop; + +int active; + +void FRejectBuilder::CheckStack (FLeaf *leaf, FThreadData *thread) +{ + PStack *p, *p2; + try + { + for (p = thread->pstack_head.next; p != NULL; p = p->next) + { + // _printf ("="); + if (p->leaf == leaf) + throw std::runtime_error("CheckStack: leaf recursion"); + for (p2 = thread->pstack_head.next; p2 != p; p2 = p2->next) + if (p2->leaf == p->leaf) + throw std::runtime_error("CheckStack: late leaf recursion"); + } + } + catch (std::runtime_error &msg) + { + printf ("%s\n", msg.what()); + } +// _printf ("\n"); +} + + +FRejectBuilder::FWinding *FRejectBuilder::AllocStackWinding (PStack *stack) const +{ + try + { + for (int i = 0; i < 3; i++) + { + if (stack->freewindings[i]) + { + stack->freewindings[i] = false; + return &stack->windings[i]; + } + } + + throw std::runtime_error("AllocStackWinding: failed"); + } + catch (std::runtime_error &msg) + { + printf ("%s\n", msg.what()); + } +} + +void FRejectBuilder::FreeStackWinding (FWinding *w, PStack *stack) const +{ + try + { + int i; + + i = w - stack->windings; + + if (i < 0 || i > 2) + return; // not from local + + if (stack->freewindings[i]) + throw std::runtime_error("FreeStackWinding: already free"); + stack->freewindings[i] = true; + } + catch (std::runtime_error &msg) + { + printf ("%s\n", msg.what()); + } +} + +/* +============== +VisChopWinding + +============== +*/ +FRejectBuilder::FWinding *FRejectBuilder::VisChopWinding (FWinding *in, PStack *stack, FLine *split) +{ + int side1, side2; + FPoint mid; + FWinding *neww; + + // determine sides for each point + side1 = PointOnSide (in->points[0], *split); + side2 = PointOnSide (in->points[1], *split); + + if (side1 <= 0 && side2 <= 0) + { // completely on front side + return in; + } + + if (side1 >= 0 && side2 >= 0) + { // completely on back side + FreeStackWinding (in, stack); + return NULL; + } + + neww = AllocStackWinding (stack); + + // generate a split point + double v2x = (double)in->points[0].x; + double v2y = (double)in->points[0].y; + double v2dx = (double)in->points[1].x - v2x; + double v2dy = (double)in->points[1].y - v2y; + double v1dx = (double)split->dx; + double v1dy = (double)split->dy; + + double den = v1dy*v2dx - v1dx*v2dy; + + if (den == 0.0) + { // parallel + return in; + } + + double v1x = (double)split->x; + double v1y = (double)split->y; + + double num = (v1x - v2x)*v1dy + (v2y - v1y)*v1dx; + double frac = num / den; + + mid.x = in->points[0].x + fixed_t(v2dx * frac); + mid.y = in->points[0].y + fixed_t(v2dy * frac); + + if (side1 <= 0) + { + neww->points[0] = in->points[0]; + neww->points[1] = mid; + } + else + { + neww->points[0] = mid; + neww->points[1] = in->points[1]; + } + + // free the original winding + FreeStackWinding (in, stack); + + return neww; +} + +/* +============== +ClipToSeperators + +Source, pass, and target are an ordering of portals. + +Generates seperating planes canidates by taking two points from source and one +point from pass, and clips target by them. + +If target is totally clipped away, that portal can not be seen through. + +Normal clip keeps target on the same side as pass, which is correct if the +order goes source, pass, target. If the order goes pass, source, target then +flipclip should be set. +============== +*/ +FRejectBuilder::FWinding *FRejectBuilder::ClipToSeperators + (FWinding *source, FWinding *pass, FWinding *target, bool flipclip, PStack *stack) +{ + int i, j; + FLine line; + int d; + bool fliptest; + + // check all combinations + for (i = 0; i < 2; i++) + { + // find a vertex of pass that makes a line that puts all of the + // vertexes of pass on the front side and all of the vertexes of + // source on the back side + for (j = 0; j < 2; j++) + { + line.x = source->points[i].x; + line.y = source->points[i].y; + line.dx = pass->points[j].x - line.x; + line.dy = pass->points[j].y - line.y; + + // + // find out which side of the generated seperating line has the + // source portal + // + fliptest = false; + d = PointOnSide (source->points[!i], line); + if (d > 0) + { // source is on the back side, so we want all + // pass and target on the front side + fliptest = false; + } + else if (d < 0) + { // source in on the front side, so we want all + // pass and target on the back side + fliptest = true; + } + else + { // colinear with source portal + continue; + } + + // + // flip the line if the source portal is backwards + // + if (fliptest) + { + line.Flip (); + } + + // + // if all of the pass portal points are now on the front side, + // this is the seperating line + // + d = PointOnSide (pass->points[!j], line); + if (d >= 0) + { // == 0: colinear with seperating plane + // > 0: points on back side; not a seperating plane + continue; + } + + // + // flip the line if we want the back side + // + if (flipclip) + { + line.Flip (); + } + +#ifdef SEPERATORCACHE + stack->seperators[flipclip][stack->numseperators[flipclip]] = line; + if (++stack->numseperators[flipclip] >= MAX_SEPERATORS) + throw exception("MAX_SEPERATORS"); +#endif + + // + // clip target by the seperating plane + // + target = VisChopWinding (target, stack, &line); + if (!target) + { // target is not visible + return NULL; + } + + break; // optimization by Antony Suter + } + } + + return target; +} + +/* +================== +RecursiveLeafFlow + +Flood fill through the leafs +If src_portal is NULL, this is the originating leaf +================== +*/ +void FRejectBuilder::RecursiveLeafFlow (int leafnum, FThreadData *thread, PStack *prevstack) +{ + PStack stack; + VPortal *p; + FLine backline; + FLeaf *leaf; + int i, j; + long *test, *might, *prevmight, *vis, more; + int pnum; + + thread->c_chains++; + + leaf = &leafs[leafnum]; +// CheckStack (leaf, thread); + + prevstack->next = &stack; + + stack.next = NULL; + stack.leaf = leaf; + stack.portal = NULL; + stack.depth = prevstack->depth + 1; + +#ifdef SEPERATORCACHE + stack.numseperators[0] = 0; + stack.numseperators[1] = 0; +#endif + + might = (long *)stack.mightsee; + vis = (long *)thread->base->portalvis; + + // check all portals for flowing into other leafs + for (i = 0; i < leaf->numportals; i++) + { + p = leaf->portals[i]; + if (p->removed) + continue; + pnum = p - portals; + + /* MrE: portal trace debug code + { + int portaltrace[] = {13, 16, 17, 37}; + pstack_t *s; + + s = &thread->pstack_head; + for (j = 0; s->next && j < sizeof(portaltrace)/sizeof(int) - 1; j++, s = s->next) + { + if (s->portal->num != portaltrace[j]) + break; + } + if (j >= sizeof(portaltrace)/sizeof(int) - 1) + { + if (p->num == portaltrace[j]) + n = 0; //traced through all the portals + } + } + */ + + if ( ! (prevstack->mightsee[pnum >> 3] & (1<<(pnum&7)) ) ) + { + continue; // can't possibly see it + } + + // if the portal can't see anything we haven't already seen, skip it + if (p->status == STAT_Done) + { + test = (long *)p->portalvis; + } + else + { + test = (long *)p->portalflood; + } + + more = 0; + prevmight = (long *)prevstack->mightsee; + for (j = 0; j < portallongs; j++) + { + might[j] = prevmight[j] & test[j]; + more |= (might[j] & ~vis[j]); + } + + if (!more && + (thread->base->portalvis[pnum>>3] & (1<<(pnum&7))) ) + { // can't see anything new + continue; + } + + // get line of portal and point into the neighbor leaf + backline = stack.portalline = p->line; + backline.Flip (); + +// c_portalcheck++; + + stack.portal = p; + stack.next = NULL; + stack.freewindings[0] = true; + stack.freewindings[1] = true; + stack.freewindings[2] = true; + + stack.pass = VisChopWinding (&p->winding, &stack, &thread->pstack_head.portalline); + if (!stack.pass) + { + continue; + } + + stack.source = VisChopWinding (prevstack->source, &stack, &backline); + if (!stack.source) + { + continue; + } + + if (!prevstack->pass) + { // the second leaf can only be blocked if coplanar + + // mark the portal as visible + thread->base->portalvis[pnum>>3] |= (1<<(pnum&7)); + + RecursiveLeafFlow (p->leaf, thread, &stack); + continue; + } + +#ifdef SEPERATORCACHE + if (stack.numseperators[0]) + { + for (n = 0; n < stack.numseperators[0]; n++) + { + stack.pass = VisChopWinding (stack.pass, &stack, &stack.seperators[0][n]); + if (!stack.pass) + break; // target is not visible + } + if (n < stack.numseperators[0]) + continue; + } + else + { + stack.pass = ClipToSeperators (prevstack->source, prevstack->pass, stack.pass, false, &stack); + } +#else + stack.pass = ClipToSeperators (stack.source, prevstack->pass, stack.pass, false, &stack); +#endif + if (!stack.pass) + continue; + +#ifdef SEPERATORCACHE + if (stack.numseperators[1]) + { + for (n = 0; n < stack.numseperators[1]; n++) + { + stack.pass = VisChopWinding (stack.pass, &stack, &stack.seperators[1][n]); + if (!stack.pass) + break; // target is not visible + } + } + else + { + stack.pass = ClipToSeperators (prevstack->pass, prevstack->source, stack.pass, true, &stack); + } +#else + stack.pass = ClipToSeperators (prevstack->pass, stack.source, stack.pass, true, &stack); +#endif + if (!stack.pass) + continue; + + // mark the portal as visible + thread->base->portalvis[pnum>>3] |= (1<<(pnum&7)); + + // flow through it for real + RecursiveLeafFlow (p->leaf, thread, &stack); + // + stack.next = NULL; + } +} + +/* +=============== +PortalFlow + +generates the portalvis bit vector +=============== +*/ +void FRejectBuilder::PortalFlow (int portalnum) +{ + FThreadData data; + int i; + VPortal *p; + int c_might, c_can; + +#ifdef MREDEBUG + printf("\r%6d", portalnum); +#endif + + p = sorted_portals[portalnum]; + + if (p->removed) + { + p->status = STAT_Done; + return; + } + + if (p->nummightsee == 0) + { + p->status = STAT_Done; + return; + } + + p->status = STAT_Working; + + c_might = p->nummightsee;//CountBits (p->portalflood, numportals); + + memset (&data, 0, sizeof(data)); + data.base = p; + + data.pstack_head.portal = p; + data.pstack_head.source = &p->winding; + data.pstack_head.portalline = p->line; + data.pstack_head.depth = 0; + for (i = 0; i < portallongs; i++) + { + ((long *)data.pstack_head.mightsee)[i] = ((long *)p->portalflood)[i]; + } + + RecursiveLeafFlow (p->leaf, &data, &data.pstack_head); + + p->status = STAT_Done; + + c_can = CountBits (p->portalvis, numportals); + + //printf ("portal:%4i mightsee:%4i cansee:%4i (%i chains)\n", + // (int)(p - portals), c_might, c_can, data.c_chains); +} + +/* +================== +RecursivePassageFlow +================== +*/ +void FRejectBuilder::RecursivePassageFlow (VPortal *portal, FThreadData *thread, PStack *prevstack) +{ + PStack stack; + VPortal *p; + FLeaf *leaf; + FPassage *passage, *nextpassage; + int i, j; + long *might, *vis, *prevmight, *cansee, *portalvis, more; + int pnum; + +// thread->c_chains++; + + leaf = &leafs[portal->leaf]; +// CheckStack (leaf, thread); + + prevstack->next = &stack; + + stack.next = NULL; +// stack.leaf = leaf; +// stack.portal = NULL; + stack.depth = prevstack->depth + 1; + + vis = (long *)thread->base->portalvis; + + passage = portal->passages; + nextpassage = passage; + // check all portals for flowing into other leafs + for (i = 0; i < leaf->numportals; i++, passage = nextpassage) + { + p = leaf->portals[i]; + if (p->removed) + continue; + nextpassage = passage->next; + pnum = p - portals; + + if ( ! (prevstack->mightsee[pnum >> 3] & (1<<(pnum&7)) ) ) + continue; // can't possibly see it + + prevmight = (long *)prevstack->mightsee; + cansee = (long *)passage->cansee; + might = (long *)stack.mightsee; + memcpy(might, prevmight, portalbytes); + portalvis = (p->status == STAT_Done) ? (long *)p->portalvis : (long *)p->portalflood; + more = 0; + for (j = 0; j < portallongs; j++) + { + if (*might) + { + *might &= *cansee++ & *portalvis++; + more |= (*might & ~vis[j]); + } + else + { + cansee++; + portalvis++; + } + might++; + } + + if (!more && + (thread->base->portalvis[pnum>>3] & (1<<(pnum&7))) ) + { // can't see anything new + continue; + } + +// stack.portal = p; + // mark the portal as visible + thread->base->portalvis[pnum>>3] |= (1<<(pnum&7)); + // flow through it for real + RecursivePassageFlow(p, thread, &stack); + // + stack.next = NULL; + } +} + +/* +=============== +PassageFlow +=============== +*/ +void FRejectBuilder::PassageFlow (int portalnum) +{ + FThreadData data; + int i; + VPortal *p; +// int c_might, c_can; + +#ifdef MREDEBUG + printf("\r%6d", portalnum); +#endif + + p = sorted_portals[portalnum]; + + if (p->removed) + { + p->status = STAT_Done; + return; + } + + p->status = STAT_Working; + +// c_might = CountBits (p->portalflood, numportals); + + memset (&data, 0, sizeof(data)); + data.base = p; + + data.pstack_head.portal = p; + data.pstack_head.source = &p->winding; + data.pstack_head.portalline = p->line; + data.pstack_head.depth = 0; + for (i = 0; i < portallongs; i++) + { + ((long *)data.pstack_head.mightsee)[i] = ((long *)p->portalflood)[i]; + } + + RecursivePassageFlow (p, &data, &data.pstack_head); + + p->status = STAT_Done; + + /* + c_can = CountBits (p->portalvis, numportals); + + qprintf ("portal:%4i mightsee:%4i cansee:%4i (%i chains)\n", + (int)(p - portals), c_might, c_can, data.c_chains); + */ +} + +/* +================== +RecursivePassagePortalFlow +================== +*/ +void FRejectBuilder::RecursivePassagePortalFlow (VPortal *portal, FThreadData *thread, PStack *prevstack) +{ + PStack stack; + VPortal *p; + FLeaf *leaf; + FLine backline; + FPassage *passage, *nextpassage; + int i, j; + long *might, *vis, *prevmight, *cansee, *portalvis, more; + int pnum; + +// thread->c_chains++; + + leaf = &leafs[portal->leaf]; +// CheckStack (leaf, thread); + + prevstack->next = &stack; + + stack.next = NULL; + stack.leaf = leaf; + stack.portal = NULL; + stack.depth = prevstack->depth + 1; + +#ifdef SEPERATORCACHE + stack.numseperators[0] = 0; + stack.numseperators[1] = 0; +#endif + + vis = (long *)thread->base->portalvis; + + passage = portal->passages; + nextpassage = passage; + // check all portals for flowing into other leafs + for (i = 0; i < leaf->numportals; i++, passage = nextpassage) + { + p = leaf->portals[i]; + if (p->removed) + continue; + nextpassage = passage->next; + pnum = p - portals; + + if ( ! (prevstack->mightsee[pnum >> 3] & (1<<(pnum&7)) ) ) + continue; // can't possibly see it + + prevmight = (long *)prevstack->mightsee; + cansee = (long *)passage->cansee; + might = (long *)stack.mightsee; + memcpy(might, prevmight, portalbytes); + portalvis = (p->status == STAT_Done) ? (long *) p->portalvis : (long *) p->portalflood; + more = 0; + for (j = 0; j < portallongs; j++) + { + if (*might) + { + *might &= *cansee++ & *portalvis++; + more |= (*might & ~vis[j]); + } + else + { + cansee++; + portalvis++; + } + might++; + } + + if (!more && + (thread->base->portalvis[pnum>>3] & (1<<(pnum&7))) ) + { // can't see anything new + continue; + } + + // get line of portal, point front into the neighbor leaf + backline = stack.portalline = p->line; + backline.Flip (); + +// c_portalcheck++; + + stack.portal = p; + stack.next = NULL; + stack.freewindings[0] = true; + stack.freewindings[1] = true; + stack.freewindings[2] = true; + + stack.pass = VisChopWinding (&p->winding, &stack, &thread->pstack_head.portalline); + if (!stack.pass) + continue; + + stack.source = VisChopWinding (prevstack->source, &stack, &backline); + if (!stack.source) + continue; + + if (!prevstack->pass) + { // the second leaf can only be blocked if colinear + + // mark the portal as visible + thread->base->portalvis[pnum>>3] |= (1<<(pnum&7)); + + RecursivePassagePortalFlow (p, thread, &stack); + continue; + } + +#ifdef SEPERATORCACHE + if (stack.numseperators[0]) + { + for (n = 0; n < stack.numseperators[0]; n++) + { + stack.pass = VisChopWinding (stack.pass, &stack, &stack.seperators[0][n]); + if (!stack.pass) + break; // target is not visible + } + if (n < stack.numseperators[0]) + continue; + } + else + { + stack.pass = ClipToSeperators (prevstack->source, prevstack->pass, stack.pass, false, &stack); + } +#else + stack.pass = ClipToSeperators (stack.source, prevstack->pass, stack.pass, false, &stack); +#endif + if (!stack.pass) + continue; + +#ifdef SEPERATORCACHE + if (stack.numseperators[1]) + { + for (n = 0; n < stack.numseperators[1]; n++) + { + stack.pass = VisChopWinding (stack.pass, &stack, &stack.seperators[1][n]); + if (!stack.pass) + break; // target is not visible + } + } + else + { + stack.pass = ClipToSeperators (prevstack->pass, prevstack->source, stack.pass, true, &stack); + } +#else + stack.pass = ClipToSeperators (prevstack->pass, stack.source, stack.pass, true, &stack); +#endif + if (!stack.pass) + continue; + + // mark the portal as visible + thread->base->portalvis[pnum>>3] |= (1<<(pnum&7)); + // flow through it for real + RecursivePassagePortalFlow(p, thread, &stack); + // + stack.next = NULL; + } +} + +/* +=============== +PassagePortalFlow +=============== +*/ +void FRejectBuilder::PassagePortalFlow (int portalnum) +{ + FThreadData data; + int i; + VPortal *p; +// int c_might, c_can; + +#ifdef MREDEBUG + printf("\r%6d", portalnum); +#endif + + p = sorted_portals[portalnum]; + + if (p->removed) + { + p->status = STAT_Done; + return; + } + + p->status = STAT_Working; + +// c_might = CountBits (p->portalflood, numportals); + + memset (&data, 0, sizeof(data)); + data.base = p; + + data.pstack_head.portal = p; + data.pstack_head.source = &p->winding; + data.pstack_head.portalline = p->line; + data.pstack_head.depth = 0; + for (i = 0; i < portallongs; i++) + { + ((long *)data.pstack_head.mightsee)[i] = ((long *)p->portalflood)[i]; + } + + RecursivePassagePortalFlow (p, &data, &data.pstack_head); + + p->status = STAT_Done; + + /* + c_can = CountBits (p->portalvis, numportals); + + qprintf ("portal:%4i mightsee:%4i cansee:%4i (%i chains)\n", + (int)(p - portals), c_might, c_can, data.c_chains); + */ +} + +FRejectBuilder::FWinding *FRejectBuilder::PassageChopWinding (FWinding *in, FWinding *out, FLine *split) +{ + int side1, side2; + FPoint mid; + FWinding *neww; + + // determine sides for each point + side1 = PointOnSide (in->points[0], *split); + side2 = PointOnSide (in->points[1], *split); + + if (side1 <= 0 && side2 <= 0) + { // completely on front side + return in; + } + + if (side1 >= 0 && side2 >= 0) + { // completely on back side + return NULL; + } + + neww = out; + + // generate a split point + double v2x = (double)in->points[0].x; + double v2y = (double)in->points[0].y; + double v2dx = (double)in->points[1].x - v2x; + double v2dy = (double)in->points[1].y - v2y; + double v1dx = (double)split->dx; + double v1dy = (double)split->dy; + + double den = v1dy*v2dx - v1dx*v2dy; + + if (den == 0.0) + { // parallel + return in; + } + + double v1x = (double)split->x; + double v1y = (double)split->y; + + double num = (v1x - v2x)*v1dy + (v2y - v1y)*v1dx; + double frac = num / den; + + mid.x = in->points[0].x + fixed_t(v2dx * frac); + mid.y = in->points[0].y + fixed_t(v2dy * frac); + + if (side1 <= 0) + { + neww->points[0] = in->points[0]; + neww->points[1] = mid; + } + else + { + neww->points[0] = mid; + neww->points[1] = in->points[1]; + } + + return neww; +} + +/* +=============== +AddSeperators +=============== +*/ +int FRejectBuilder::AddSeperators (FWinding *source, FWinding *pass, bool flipclip, + FLine *seperators, int maxseperators) +{ + int i, j; + FLine line; + int d; + int numseperators; + bool fliptest; + + numseperators = 0; + // check all combinations + for (i = 0; i < 2; i++) + { + try + { + // find a vertex of pass that makes a plane that puts all of the + // vertexes of pass on the front side and all of the vertexes of + // source on the back side + for (j = 0; j < 2; j++) + { + line.x = source->points[i].x; + line.y = source->points[i].y; + line.dx = pass->points[j].x - line.x; + line.dy = pass->points[j].y - line.y; + + // + // find out which side of the generated seperating plane has the + // source portal + // + fliptest = false; + d = PointOnSide (source->points[!i], line); + if (d > 0) + { // source is on the back side, so we want all + // pass and target on the front side + fliptest = false; + } + else if (d < 0) + { // source in on the front side, so we want all + // pass and target on the back side + fliptest = true; + } + else + { // colinear with source portal + continue; + } + + // + // flip the line if the source portal is backwards + // + if (fliptest) + { + line.Flip (); + } + + // + // if all of the pass portal points are now on the positive side, + // this is the seperating plane + // + d = PointOnSide (pass->points[!j], line); + if (d >= 0) + { // == 0: colinear with seperating plane + // > 0: points on back side; not a seperating plane + continue; + } + + // + // flip the line if we want the back side + // + if (flipclip) + { + line.Flip (); + } + + if (numseperators >= maxseperators) + throw std::runtime_error("max seperators"); + seperators[numseperators] = line; + numseperators++; + break; + } + } + catch (std::runtime_error &msg) + { + printf ("%s\n", msg.what()); + } + } + return numseperators; +} + +/* +=============== +CreatePassages + +MrE: create passages from one portal to all the portals in the leaf the portal leads to + every passage has a cansee bit string with all the portals that can be + seen through the passage +=============== +*/ +void FRejectBuilder::CreatePassages (int portalnum) +{ + int i, j, k, numseperators, numsee; + VPortal *portal, *p, *target; + FLeaf *leaf; + FPassage *passage, *lastpassage; + FLine seperators[MAX_SEPERATORS*2]; + FWinding in, out, *res; + +#ifdef MREDEBUG + printf("\r%6d", portalnum); +#endif + + portal = sorted_portals[portalnum]; + + if (portal->removed) + { + portal->status = STAT_Done; + return; + } + + lastpassage = NULL; + leaf = &leafs[portal->leaf]; + for (i = 0; i < leaf->numportals; i++) + { + target = leaf->portals[i]; + if (target->removed) + continue; + + passage = (FPassage *) malloc(sizeof(FPassage) + portalbytes); + memset(passage, 0, sizeof(FPassage) + portalbytes); + numseperators = AddSeperators(&portal->winding, &target->winding, false, seperators, MAX_SEPERATORS*2); + numseperators += AddSeperators(&target->winding, &portal->winding, true, &seperators[numseperators], MAX_SEPERATORS*2-numseperators); + + passage->next = NULL; + if (lastpassage) + lastpassage->next = passage; + else + portal->passages = passage; + lastpassage = passage; + + numsee = 0; + //create the passage->cansee + for (j = 0; j < numportals; j++) + { + p = &portals[j]; + if (p->removed) + continue; + if ( ! (target->portalflood[j >> 3] & (1<<(j&7)) ) ) + continue; + if ( ! (portal->portalflood[j >> 3] & (1<<(j&7)) ) ) + continue; + for (k = 0; k < numseperators; k++) + { + //check if completely on the back of the seperator line + if (PointOnSide (p->line, seperators[k]) > 0) + { + FPoint pt2 = p->line; + pt2.x += p->line.dx; + pt2.y += p->line.dy; + if (PointOnSide (pt2, seperators[k]) > 0) + { + break; + } + } + } + if (k < numseperators) + { + continue; + } + memcpy(&in, &p->winding, sizeof(FWinding)); + for (k = 0; k < numseperators; k++) + { + res = PassageChopWinding(&in, &out, &seperators[k]); + if (res == &out) + memcpy(&in, &out, sizeof(FWinding)); + if (res == NULL) + break; + } + if (k < numseperators) + continue; + passage->cansee[j >> 3] |= (1<<(j&7)); + numsee++; + } + } +} + +void FRejectBuilder::PassageMemory () +{ + int i, j, totalmem, totalportals; + VPortal *portal, *target; + FLeaf *leaf; + + totalmem = 0; + totalportals = 0; + for (i = 0; i < numportals; i++) + { + portal = sorted_portals[i]; + if (portal->removed) + continue; + leaf = &leafs[portal->leaf]; + for (j = 0; j < leaf->numportals; j++) + { + target = leaf->portals[j]; + if (target->removed) + continue; + totalmem += sizeof(FPassage) + portalbytes; + totalportals++; + } + } + printf("\n%7i average number of passages per leaf\n", totalportals / numportals); + printf("%7i MB required passage memory\n", totalmem >> 10 >> 10); +} + +/* +=============================================================================== + +This is a rough first-order aproximation that is used to trivially reject some +of the final calculations. + + +Calculates portalfront and portalflood bit vectors + +thinking about: + +typedef struct passage_s +{ + struct passage_s *next; + struct portal_s *to; + struct sep_s *seperators; + byte *mightsee; +} passage_t; + +typedef struct portal_s +{ + struct passage_s *passages; + int leaf; // leaf portal faces into +} portal_s; + +leaf = portal->leaf +clear +for all portals + + +calc portal visibility + clear bit vector + for all passages + passage visibility + + +for a portal to be visible to a passage, it must be on the front of +all seperating planes, and both portals must be behind the new portal + +=============================================================================== +*/ + +int c_flood, c_vis; + + +/* +================== +SimpleFlood + +================== +*/ +void FRejectBuilder::SimpleFlood (VPortal *srcportal, int leafnum) +{ + int i; + FLeaf *leaf; + VPortal *p; + int pnum; + + leaf = &leafs[leafnum]; + + for (i = 0; i < leaf->numportals; i++) + { + p = leaf->portals[i]; + if (p->removed) + continue; + pnum = p - portals; + if ((srcportal->portalfront[pnum>>3] & (1<<(pnum&7))) && + !(srcportal->portalflood[pnum>>3] & (1<<(pnum&7))) ) + { + srcportal->portalflood[pnum>>3] |= (1<<(pnum&7)); + SimpleFlood (srcportal, p->leaf); + } + } +} + +/* +============== +BasePortalVis +============== +*/ +void FRejectBuilder::BasePortalVis (int portalnum) +{ + int j, p1, p2; + VPortal *tp, *p; + + p = portals+portalnum; + + if (p->removed) + return; + + p->portalfront = new BYTE[portalbytes]; + memset (p->portalfront, 0, portalbytes); + + p->portalflood = new BYTE[portalbytes]; + memset (p->portalflood, 0, portalbytes); + + p->portalvis = new BYTE[portalbytes]; + memset (p->portalvis, 0, portalbytes); + + for (j = 0, tp = portals; j < numportals; j++, tp++) + { + if (j == portalnum) + continue; + if (tp->removed) + continue; + + //p->portalfront[j>>3] |= (1<<(j&7)); + //continue; + + // The target portal must be in front of this one + if ((p1 = PointOnSide (tp->winding.points[0], p->line)) > 0 || + (p2 = PointOnSide (tp->winding.points[1], p->line)) > 0) + { + continue; + } + + // Portals must not be colinear + if ((p1 | p2) == 0) + { + continue; + } + + // This portal must be behind the target portal + if (PointOnSide (p->winding.points[0], tp->line) < 0 || + PointOnSide (p->winding.points[1], tp->line) < 0) + { + continue; + } + + p->portalfront[j>>3] |= (1<<(j&7)); + } + + SimpleFlood (p, p->leaf); + + p->nummightsee = CountBits (p->portalflood, numportals); +// _printf ("portal %i: %i mightsee\n", portalnum, p->nummightsee); + c_flood += p->nummightsee; +} + + + + + +/* +=============================================================================== + +This is a second order aproximation + +Calculates portalvis bit vector + +WAAAAAAY too slow. + +=============================================================================== +*/ + +/* +================== +RecursiveLeafBitFlow + +================== +*/ +void FRejectBuilder::RecursiveLeafBitFlow (int leafnum, BYTE *mightsee, BYTE *cansee) +{ + VPortal *p; + FLeaf *leaf; + int i, j; + long more; + int pnum; + BYTE newmight[MAX_PORTALS/8]; + + leaf = &leafs[leafnum]; + + // check all portals for flowing into other leafs + for (i = 0; i < leaf->numportals; i++) + { + p = leaf->portals[i]; + if (p->removed) + continue; + pnum = p - portals; + + // if some previous portal can't see it, skip + if (! (mightsee[pnum>>3] & (1<<(pnum&7)) ) ) + continue; + + // if this portal can see some portals we mightsee, recurse + more = 0; + for (j = 0; j < portallongs; j++) + { + ((long *)newmight)[j] = ((long *)mightsee)[j] & ((long *)p->portalflood)[j]; + more |= ((long *)newmight)[j] & ~((long *)cansee)[j]; + } + + if (!more) + continue; // can't see anything new + + cansee[pnum>>3] |= (1<<(pnum&7)); + + RecursiveLeafBitFlow (p->leaf, newmight, cansee); + } +} + +/* +============== +BetterPortalVis +============== +*/ +void FRejectBuilder::BetterPortalVis (int portalnum) +{ + VPortal *p; + + p = portals+portalnum; + + if (p->removed) + return; + + RecursiveLeafBitFlow (p->leaf, p->portalflood, p->portalvis); + + // build leaf vis information + p->nummightsee = CountBits (p->portalvis, numportals); + c_vis += p->nummightsee; +} + + From 9e5bf01b735b327763595c4871a0076c62029aa0 Mon Sep 17 00:00:00 2001 From: Dasho Danger Date: Sat, 20 Mar 2021 19:43:04 +0430 Subject: [PATCH 11/20] Adjusted per-engine GL node options --- gui/g_doom.cc | 9 ++++++--- zdbsp_src/zdmain.cc | 1 + zdbsp_src/zdmain.h | 1 + 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/gui/g_doom.cc b/gui/g_doom.cc index 7bebdcae6e..a433c11537 100644 --- a/gui/g_doom.cc +++ b/gui/g_doom.cc @@ -853,7 +853,8 @@ static bool DM_BuildNodes(const char *filename, const char *out_name) { options.build_nodes = true; options.build_gl_nodes = false; - options.reject_mode = ERM_Rebuild; + options.build_gl_only = false; + options.reject_mode = ERM_CreateZeroes; options.check_polyobjs = false; options.compress_nodes = false; options.compress_gl_nodes = false; @@ -862,11 +863,12 @@ static bool DM_BuildNodes(const char *filename, const char *out_name) else if (current_engine == "boom") { options.build_nodes = true; - options.build_gl_nodes = false; + options.build_gl_nodes = true; + options.build_gl_only = true; options.reject_mode = ERM_Rebuild; options.check_polyobjs = false; options.compress_nodes = true; - options.compress_gl_nodes = true; + options.compress_gl_nodes = false; options.force_compression = false; } if (zdmain(filename, options) != 0) @@ -888,6 +890,7 @@ static bool DM_BuildNodes(const char *filename, const char *out_name) zdbsp_options options; options.build_nodes = true; options.build_gl_nodes = true; + options.build_gl_only = true; options.reject_mode = ERM_Rebuild; options.check_polyobjs = true; options.compress_nodes = true; diff --git a/zdbsp_src/zdmain.cc b/zdbsp_src/zdmain.cc index 996b1ef301..73a82aa375 100644 --- a/zdbsp_src/zdmain.cc +++ b/zdbsp_src/zdmain.cc @@ -111,6 +111,7 @@ int zdmain (const char *filename, zdbsp_options options) CompressNodes = options.compress_nodes; CompressGLNodes = options.compress_gl_nodes; ForceCompression = options.force_compression; + GLOnly = options.build_gl_only; ShowVersion(); diff --git a/zdbsp_src/zdmain.h b/zdbsp_src/zdmain.h index 9367c687b7..85aefcdcb4 100644 --- a/zdbsp_src/zdmain.h +++ b/zdbsp_src/zdmain.h @@ -4,6 +4,7 @@ typedef struct zdbsp_options { bool build_nodes; bool build_gl_nodes; +bool build_gl_only; ERejectMode reject_mode; // = ERM_DontTouch; bool check_polyobjs; bool compress_nodes; From 917687d643c446c65a9ad2ae962dab915e8e7b1b Mon Sep 17 00:00:00 2001 From: dashodanger Date: Sun, 21 Mar 2021 10:13:52 +0430 Subject: [PATCH 12/20] -Condensed ZDoom/GZDoom into single engine listing -Removed debug printf statements from reject builder --- engines/zdoom.lua | 46 ++++----------------- games/doom/fabs/hall/dem_conveyorh_j.lua | 4 +- games/doom/fabs/hall/dem_conveyorh_k.lua | 8 ++-- games/doom/fabs/hall/dem_pipeline_c.lua | 2 +- games/doom/fabs/hall/dem_pipeline_k.lua | 4 +- games/doom/fabs/item/dem_item_closets.lua | 2 +- games/doom/fabs/item/dem_secret_closets.lua | 2 +- games/doom/fabs/picture/dem_pic_nature.lua | 2 +- gui/g_doom.cc | 4 +- modules/complex_doom.lua | 2 +- modules/enemy_placement.lua | 2 +- modules/gzdoom_fauna.lua | 2 +- modules/heretic/zdoom_specials.lua | 2 +- modules/stealth_mons.lua | 2 +- modules/ui_zdoom_map_options.lua | 2 +- modules/zdoom_armaetus_epic_textures.lua | 2 +- modules/zdoom_frozsoul_sound.lua | 2 +- modules/zdoom_marines.lua | 2 +- modules/zdoom_specials.lua | 2 +- zdbsp_src/rejectbuilder.cc | 1 - zdbsp_src/vis.cc | 2 - 21 files changed, 33 insertions(+), 64 deletions(-) diff --git a/engines/zdoom.lua b/engines/zdoom.lua index 66909f8bb5..7b593efdfe 100644 --- a/engines/zdoom.lua +++ b/engines/zdoom.lua @@ -40,33 +40,6 @@ ZDOOM.ENTITIES = ZDOOM.PARAMETERS = -{ - -- TODO -} - - -OB_ENGINES["zdoom"] = -{ - label = _("ZDoom"), - - extends = "boom", - - game = - { - chex3=1, doom1=1, doom2=1, heretic=1, hexen=1, strife=1, - }, - - tables = - { - ZDOOM - } -} - ----------------------------------------------------------------- - -GZDOOM = { } - -GZDOOM.PARAMETERS = { bridges = true, extra_floors = true, @@ -74,8 +47,7 @@ GZDOOM.PARAMETERS = tga_images = true } - -function GZDOOM.setup() +function ZDOOM.setup() -- extrafloors : use Legacy types gui.property("ef_solid_type", 281) gui.property("ef_liquid_type", 301) @@ -85,27 +57,27 @@ function GZDOOM.setup() gui.property("ef_thing_mode", 1) end - -OB_ENGINES["gzdoom"] = +OB_ENGINES["zdoom"] = { label = _("GZDoom"), - priority = -1, -- keep at bottom with ZDoom + priority = -1, - extends = "zdoom", + extends = "boom", game = { - chex3=1, doom1=1, doom2=1, heretic=1, hexen=1, strife=1 + chex3=1, doom1=1, doom2=1, heretic=1, hexen=1, strife=1, }, tables = { - GZDOOM + ZDOOM }, - + hooks = { - setup = GZDOOM.setup + setup = ZDOOM.setup } + } diff --git a/games/doom/fabs/hall/dem_conveyorh_j.lua b/games/doom/fabs/hall/dem_conveyorh_j.lua index 7b29adc5bd..28e5664aca 100644 --- a/games/doom/fabs/hall/dem_conveyorh_j.lua +++ b/games/doom/fabs/hall/dem_conveyorh_j.lua @@ -6,7 +6,7 @@ PREFABS.Hallway_conveyorh_term1 = { file = "hall/dem_conveyorh_j.wad", map = "MAP01", - engine = "gzdoom", + engine = "zdoom", kind = "terminator", theme = "hell", @@ -112,7 +112,7 @@ PREFABS.Hallway_conveyorh_term4 = map = "MAP04", - engine = "gzdoom", + engine = "zdoom", style = "doors", diff --git a/games/doom/fabs/hall/dem_conveyorh_k.lua b/games/doom/fabs/hall/dem_conveyorh_k.lua index fdfa5b8912..be3d337c03 100644 --- a/games/doom/fabs/hall/dem_conveyorh_k.lua +++ b/games/doom/fabs/hall/dem_conveyorh_k.lua @@ -6,7 +6,7 @@ PREFABS.Hallway_conveyorh_locked_red1 = { file = "hall/dem_conveyorh_k.wad", map = "MAP01", - engine = "gzdoom", + engine = "zdoom", theme = "hell", @@ -31,7 +31,7 @@ PREFABS.Hallway_conveyorh_locked_blue1 = { template = "Hallway_conveyorh_locked_red1", map = "MAP01", - engine = "gzdoom", + engine = "zdoom", key = "k_blue", @@ -44,7 +44,7 @@ PREFABS.Hallway_conveyorh_locked_yellow1 = { template = "Hallway_conveyorh_locked_red1", map = "MAP01", - engine = "gzdoom", + engine = "zdoom", key = "k_yellow", @@ -186,7 +186,7 @@ PREFABS.Hallway_conveyorh_barred1 = { file = "hall/dem_conveyorh_k.wad", map = "MAP04", - engine = "gzdoom", + engine = "zdoom", kind = "terminator", group = "conveyorh", diff --git a/games/doom/fabs/hall/dem_pipeline_c.lua b/games/doom/fabs/hall/dem_pipeline_c.lua index 71c12b34c3..d04692a573 100644 --- a/games/doom/fabs/hall/dem_pipeline_c.lua +++ b/games/doom/fabs/hall/dem_pipeline_c.lua @@ -17,7 +17,7 @@ PREFABS.Hallway_pipeline_c1 = seed_w = 2, seed_h = 2, - engine = "gzdoom", + engine = "zdoom", sound = "Pipeline", diff --git a/games/doom/fabs/hall/dem_pipeline_k.lua b/games/doom/fabs/hall/dem_pipeline_k.lua index b03ed67aba..2c6d784360 100644 --- a/games/doom/fabs/hall/dem_pipeline_k.lua +++ b/games/doom/fabs/hall/dem_pipeline_k.lua @@ -73,7 +73,7 @@ PREFABS.Hallway_pipeline_locked_red2 = y_fit = "top", deep = 16, - engine = "gzdoom", + engine = "zdoom", sound = "Pipeline", @@ -130,7 +130,7 @@ PREFABS.Hallway_pipeline_barred1 = deep = 16, - engine = "gzdoom", + engine = "zdoom", sound = "Pipeline", diff --git a/games/doom/fabs/item/dem_item_closets.lua b/games/doom/fabs/item/dem_item_closets.lua index f7dd950a98..8d841a6ce4 100644 --- a/games/doom/fabs/item/dem_item_closets.lua +++ b/games/doom/fabs/item/dem_item_closets.lua @@ -693,7 +693,7 @@ PREFABS.Item_dem_campsiteC_closet = file = "item/dem_item_closets.wad", map = "MAP21", - engine = "gzdoom", + engine = "zdoom", theme = "!hell", diff --git a/games/doom/fabs/item/dem_secret_closets.lua b/games/doom/fabs/item/dem_secret_closets.lua index d3623aedad..e2cd3d8f4d 100644 --- a/games/doom/fabs/item/dem_secret_closets.lua +++ b/games/doom/fabs/item/dem_secret_closets.lua @@ -407,7 +407,7 @@ PREFABS.Item_dem_campsiteC_secretcloset = file = "item/dem_secret_closets.wad", map = "MAP13", - engine = "gzdoom", + engine = "zdoom", theme = "!hell", diff --git a/games/doom/fabs/picture/dem_pic_nature.lua b/games/doom/fabs/picture/dem_pic_nature.lua index 64d47fd8a8..2ac9cdb125 100644 --- a/games/doom/fabs/picture/dem_pic_nature.lua +++ b/games/doom/fabs/picture/dem_pic_nature.lua @@ -821,7 +821,7 @@ PREFABS.Pic_dem_campsiteC = file = "picture/dem_pic_nature.wad", map = "MAP52", - engine = "gzdoom", + engine = "zdoom", theme = "!hell", diff --git a/gui/g_doom.cc b/gui/g_doom.cc index a433c11537..9c0da14481 100644 --- a/gui/g_doom.cc +++ b/gui/g_doom.cc @@ -846,7 +846,7 @@ static bool DM_BuildNodes(const char *filename, const char *out_name) // Node building and map format options are a moot point for non-ZDoom engines - if (current_engine != "zdoom" && current_engine != "gzdoom") + if (current_engine != "zdoom") { zdbsp_options options; if (current_engine == "nolimit") @@ -972,7 +972,7 @@ bool doom_game_interface_c::Start(const char *preset) current_engine = main_win->game_box->engine->GetID(); map_format = main_win->left_mods->FindID("ui_zdoom_map_options")->FindOpt("map_format")->GetID(); build_nodes = main_win->left_mods->FindID("ui_zdoom_map_options")->FindOpt("build_nodes")->GetID(); - if ((current_engine == "zdoom" || current_engine == "gzdoom") && map_format == "udmf") + if (current_engine == "zdoom" && map_format == "udmf") { UDMF_mode = true; } diff --git a/modules/complex_doom.lua b/modules/complex_doom.lua index 63dda8006b..ae6b8d5094 100644 --- a/modules/complex_doom.lua +++ b/modules/complex_doom.lua @@ -716,7 +716,7 @@ OB_MODULES["complex_doom"] = tooltip = "Alters to better fit the difficulty that Complex Doom provides. Do not use with 'Harder Enemy Setup' addon, will conflict.", -- Zandronum *SHOULD* work with v27, otherwise will remove it. - engine = { zdoom=1, gzdoom=1, skulltag=1 }, + engine = "zdoom", tables = { diff --git a/modules/enemy_placement.lua b/modules/enemy_placement.lua index 02153eb6e9..3bd1452ab0 100644 --- a/modules/enemy_placement.lua +++ b/modules/enemy_placement.lua @@ -493,7 +493,7 @@ OB_MODULES["harder_enemy"] = tooltip = "Changes enemy placement and thus makes overall gameplay a bit to fair bit harder, depending if you use additional mods in the case of GZDoom.", - engine = { zdoom=1, gzdoom=1, skulltag=1, limit=1, boom=1 }, + engine = { zdoom=1, limit=1, boom=1 }, tables = { diff --git a/modules/gzdoom_fauna.lua b/modules/gzdoom_fauna.lua index a38c0e5043..f57ee7ffde 100644 --- a/modules/gzdoom_fauna.lua +++ b/modules/gzdoom_fauna.lua @@ -512,7 +512,7 @@ OB_MODULES["fauna_module"] = side = "left", priority = 68, - engine = { gzdoom=1 }, + engine = "zdoom", hooks = { diff --git a/modules/heretic/zdoom_specials.lua b/modules/heretic/zdoom_specials.lua index f235904f0f..0bf4ed1e24 100644 --- a/modules/heretic/zdoom_specials.lua +++ b/modules/heretic/zdoom_specials.lua @@ -1132,7 +1132,7 @@ OB_MODULES["zdoom_specials_heretic"] = priority = 68, - engine = { zdoom=1, gzdoom=1, skulltag=1 }, + engine = "zdoom", hooks = { diff --git a/modules/stealth_mons.lua b/modules/stealth_mons.lua index e0081cad5c..0c18722814 100644 --- a/modules/stealth_mons.lua +++ b/modules/stealth_mons.lua @@ -263,7 +263,7 @@ OB_MODULES["stealth_mons"] = game = "doomish", - engine = { edge=1, zdoom=1, gzdoom=1, skulltag=1 }, + engine = "zdoom", tables = { diff --git a/modules/ui_zdoom_map_options.lua b/modules/ui_zdoom_map_options.lua index 52358903e7..2094f32e0f 100644 --- a/modules/ui_zdoom_map_options.lua +++ b/modules/ui_zdoom_map_options.lua @@ -34,7 +34,7 @@ OB_MODULES["ui_zdoom_map_options"] = { label = _("Map Build Options"), - engine = { zdoom=1, gzdoom=1 }, + engine = "zdoom", side = "left", priority = 105, diff --git a/modules/zdoom_armaetus_epic_textures.lua b/modules/zdoom_armaetus_epic_textures.lua index c1b62165fc..1918bd6ca6 100644 --- a/modules/zdoom_armaetus_epic_textures.lua +++ b/modules/zdoom_armaetus_epic_textures.lua @@ -1032,7 +1032,7 @@ OB_MODULES["armaetus_epic_textures"] = side = "left", priority = 70, - engine = { zdoom=1, gzdoom=1, skulltag=1 }, + engine = "zdoom", game = "doomish", diff --git a/modules/zdoom_frozsoul_sound.lua b/modules/zdoom_frozsoul_sound.lua index fcfb1fd4b1..565b22f307 100644 --- a/modules/zdoom_frozsoul_sound.lua +++ b/modules/zdoom_frozsoul_sound.lua @@ -244,7 +244,7 @@ OB_MODULES["zdoom_ambient_sound"] = priority = 69, - engine = { zdoom=1, gzdoom=1 }, + engine = "zdoom", hooks = { diff --git a/modules/zdoom_marines.lua b/modules/zdoom_marines.lua index 31efa57e0b..45e62428c9 100644 --- a/modules/zdoom_marines.lua +++ b/modules/zdoom_marines.lua @@ -217,7 +217,7 @@ OB_MODULES["zdoom_marines"] = game = "doomish", - engine = { zdoom=1, gzdoom=1, skulltag=1 }, + engine = "zdoom", tables = { diff --git a/modules/zdoom_specials.lua b/modules/zdoom_specials.lua index aceaa0577c..f863716d2b 100644 --- a/modules/zdoom_specials.lua +++ b/modules/zdoom_specials.lua @@ -1231,7 +1231,7 @@ OB_MODULES["zdoom_specials"] = priority = 68, - engine = { zdoom=1, gzdoom=1, skulltag=1 }, + engine = "zdoom", hooks = { diff --git a/zdbsp_src/rejectbuilder.cc b/zdbsp_src/rejectbuilder.cc index f4a968a5b3..7767c62c46 100644 --- a/zdbsp_src/rejectbuilder.cc +++ b/zdbsp_src/rejectbuilder.cc @@ -124,7 +124,6 @@ void FRejectBuilder::LoadPortals () FLeaf *l; FWinding *w; - printf("GL SUBS: %d\n", Level.NumGLSubsectors); portalclusters = Level.NumGLSubsectors; for (numportals = 0, i = 0; i < Level.NumGLSegs; ++i) diff --git a/zdbsp_src/vis.cc b/zdbsp_src/vis.cc index 8294065522..5ae726a18f 100644 --- a/zdbsp_src/vis.cc +++ b/zdbsp_src/vis.cc @@ -284,8 +284,6 @@ void FRejectBuilder::CalcVis () // assemble the leaf vis lists by oring and compressing the portal lists // printf ("creating leaf vis...\n"); - printf ("PORTAL CLUSTERS: %d\n", portalclusters); - printf ("TOTAL VIS: %d\n", totalvis); for (int i = 0; i < portalclusters; i++) { ClusterMerge (i); From a39275439e1d54a0bc5ee81bb8a85bcdcd5c6a70 Mon Sep 17 00:00:00 2001 From: dashodanger Date: Sun, 21 Mar 2021 12:38:42 +0430 Subject: [PATCH 13/20] -Enabled FastVis option in GL Reject Builder -Added non-GL reject builder (needs better algorithm) --- Makefile | 3 +- gui/g_doom.cc | 1 - ...ctbuilder.cc.bak => rejectbuilder.cc-slow} | 0 zdbsp_src/Unused/rejectbuilder.cpp-slow | 278 --------------- zdbsp_src/Unused/rejectbuilder.h.bak | 40 --- zdbsp_src/processor.cc | 11 +- zdbsp_src/rejectbuilder_nogl.cc | 325 ++++++++++++++++++ zdbsp_src/rejectbuilder_nogl.h | 77 +++++ zdbsp_src/vis.cc | 2 +- zdbsp_src/zdbsp.h | 3 +- 10 files changed, 416 insertions(+), 324 deletions(-) rename zdbsp_src/Unused/{rejectbuilder.cc.bak => rejectbuilder.cc-slow} (100%) delete mode 100644 zdbsp_src/Unused/rejectbuilder.cpp-slow delete mode 100644 zdbsp_src/Unused/rejectbuilder.h.bak create mode 100644 zdbsp_src/rejectbuilder_nogl.cc create mode 100644 zdbsp_src/rejectbuilder_nogl.h diff --git a/Makefile b/Makefile index 4d4bce217d..40184f5f62 100644 --- a/Makefile +++ b/Makefile @@ -174,7 +174,8 @@ ZDBSP_OBJS= \ $(OBJ_DIR)/zdbsp/zdwad.o \ $(OBJ_DIR)/zdbsp/nodebuild.o \ $(OBJ_DIR)/zdbsp/rejectbuilder.o \ - $(OBJ_DIR)/zdbsp/vis.o \ + $(OBJ_DIR)/zdbsp/rejectbuilder_nogl.o \ + $(OBJ_DIR)/zdbsp/vis.o \ $(OBJ_DIR)/zdbsp/visflow.o \ $(OBJ_DIR)/zdbsp/nodebuild_events.o \ $(OBJ_DIR)/zdbsp/nodebuild_extract.o \ diff --git a/gui/g_doom.cc b/gui/g_doom.cc index 9c0da14481..85a35ba1a3 100644 --- a/gui/g_doom.cc +++ b/gui/g_doom.cc @@ -898,7 +898,6 @@ static bool DM_BuildNodes(const char *filename, const char *out_name) options.force_compression = true; if (zdmain(filename, options) != 0) { - zdbsp_options options; Main_ProgStatus(_("ZDBSP Error!")); return false; } diff --git a/zdbsp_src/Unused/rejectbuilder.cc.bak b/zdbsp_src/Unused/rejectbuilder.cc-slow similarity index 100% rename from zdbsp_src/Unused/rejectbuilder.cc.bak rename to zdbsp_src/Unused/rejectbuilder.cc-slow diff --git a/zdbsp_src/Unused/rejectbuilder.cpp-slow b/zdbsp_src/Unused/rejectbuilder.cpp-slow deleted file mode 100644 index 069fca218e..0000000000 --- a/zdbsp_src/Unused/rejectbuilder.cpp-slow +++ /dev/null @@ -1,278 +0,0 @@ -// This is the same algorithm used by DoomBSP: -// -// Represent each sector by its bounding box. Then for each pair of -// sectors, see if any chains of one-sided lines can walk from one -// side of the convex hull for that pair to the other side. -// -// It works, but it's far from being perfect. It's quite easy for -// this algorithm to consider two sectors as being visible from -// each other when they are really not. But it won't erroneously -// flag two sectors as obstructed when they're really not, and that's -// the only thing that really matters when building a REJECT lump. - -#include -#include - -#include "zdbsp.h" -#include "nodebuild.h" -#include "rejectbuilder.h" -#include "templates.h" - -FRejectBuilder::FRejectBuilder (FLevel &level) - : Level (level) -{ - int i; - - i = Level.NumGLSubsectors * Level.NumGLSubsectors; - SubSeeMatrix = new BYTE[i]; - memset (SubSeeMatrix, 0, i); - - SegSubsectors = new WORD[Level.NumGLSegs]; - for (i = 0; i < Level.NumGLSubsectors; ++i) - { - for (int j = 0; j < Level.GLSubsectors[i].numlines; ++j) - { - SegSubsectors[j + Level.GLSubsectors[i].firstline] = i; - } - } - - BuildReject (); -} - -FRejectBuilder::~FRejectBuilder () -{ - delete[] SubSeeMatrix; - delete[] SegSubsectors; -} - -BYTE *FRejectBuilder::GetReject () -{ - int i, j; - - int rejectSize = (Level.NumSectors*Level.NumSectors + 7) / 8; - BYTE *reject = new BYTE[rejectSize]; - memset (reject, 0xff, rejectSize); - - int pvs_size = (Level.NumGLSubsectors * Level.NumGLSubsectors) + 7 / 8; - Level.GLPVS = new BYTE[pvs_size]; - Level.GLPVSSize = pvs_size; - memset (Level.GLPVS, 0, pvs_size); - - for (i = 0; i < Level.NumGLSubsectors; ++i) - { - int row = i*Level.NumGLSubsectors; - int sector1 = - Level.Sides[ - Level.Lines[ - Level.GLSegs[ - Level.GLSubsectors[i].firstline].linedef] - .sidenum[ - Level.GLSegs[ - Level.GLSubsectors[i].firstline].side]] - .sector; - int srow = sector1*Level.NumSectors; - for (j = 0; j < Level.NumGLSubsectors; ++j) - { - if (SubSeeMatrix[row + j]) - { - int sector2 = - Level.Sides[ - Level.Lines[ - Level.GLSegs[ - Level.GLSubsectors[j].firstline].linedef] - .sidenum[ - Level.GLSegs[ - Level.GLSubsectors[j].firstline].side]] - .sector; - int l = (srow + sector2) >> 3; - int r = (srow + sector2) & 7; - reject[l] &= ~(1 << r); - - l = (row + j) >> 3; - r = (row + j) & 7; - Level.GLPVS[l] |= 1 << r; - } - } - } - - return reject; -} - -void FRejectBuilder::BuildReject () -{ - int s1, s2; - - for (s1 = 0; s1 < Level.NumGLSubsectors; ++s1) - { - //printf (" Reject: %3d%%\r",s1*100/Level.NumGLSubsectors); - printf ("%d/%d\r", s1, Level.NumGLSubsectors); - - // A subsector can always see itself - SourceRow = s1 * Level.NumGLSubsectors; - SubSeeMatrix[SourceRow + s1] = 1; - - WORD pusher = s1; - - for (s2 = 0; s2 < Level.GLSubsectors[s1].numlines; ++s2) - { - int segnum = s2 + Level.GLSubsectors[s1].firstline; - const MapSegGL *seg = &Level.GLSegs[segnum]; - if (seg->partner == NO_INDEX) - { - continue; - } - - SourceSeg = segnum; - SegRow = segnum * Level.NumGLSegs; - - TracePath (s1, seg); - } - } - printf (" Reject: 100%%\n"); -} - -inline const WideVertex *FRejectBuilder::GetVertex (WORD vertnum) -{ - if (vertnum & 0x8000) - { - return &Level.GLVertices[vertnum & 0x7fff]; - } - else - { - return &Level.Vertices[vertnum]; - } -} - -void FRejectBuilder::TracePath (int subsector, const MapSegGL *window) -{ - // Neighboring subsectors can always see each other - SubSeeMatrix[SourceRow + SegSubsectors[window->partner]] = 1; - const MapSubsector *backsub = &Level.GLSubsectors[SegSubsectors[window->partner]]; - - Portal source; - - source.Subsector = backsub; - source.Left = GetVertex (window->v1); - source.Right = GetVertex (window->v2); - - PortalStack.Clear (); - PortalStack.Push (source); - - fixed_t wdx = source.Right->x - source.Left->x; - fixed_t wdy = source.Right->y - source.Left->y; - -// printf ("start window %d\n", window - Level.GLSegs); - - for (int i = 0; i < backsub->numlines; ++i) - { - int segnum = backsub->firstline + i; - - if (segnum == window->partner) - { - continue; - } - - const MapSegGL *cseg = &Level.GLSegs[segnum]; - - if (cseg->partner == NO_INDEX) - { - continue; - } - - const WideVertex *cv1 = GetVertex (cseg->v1); - const WideVertex *cv2 = GetVertex (cseg->v2); - - if (FNodeBuilder::PointOnSide (cv1->x, cv1->y, source.Left->x, source.Left->y, wdx, wdy) == 0 && - FNodeBuilder::PointOnSide (cv2->x, cv2->y, source.Left->x, source.Left->y, wdx, wdy) == 0) - { - continue; - } - - TracePathDeep (cseg); - } -} - -void FRejectBuilder::TracePathDeep (const MapSegGL *window) -{ - SubSeeMatrix[SourceRow + SegSubsectors[window->partner]] = 1; - const MapSubsector *backsub = &Level.GLSubsectors[SegSubsectors[window->partner]]; - size_t j; - - for (j = PortalStack.Size(); j-- > 0; ) - { - if (PortalStack[j].Subsector == backsub) - { - return; - } - } - - Portal entrance; - - entrance.Subsector = backsub; - entrance.Left = GetVertex (window->v1); - entrance.Right = GetVertex (window->v2); - PortalStack.Push (entrance); - - fixed_t wdx = entrance.Right->x - entrance.Left->x; - fixed_t wdy = entrance.Right->y - entrance.Left->y; - - //printf ("deep through %d\n", window - Level.GLSegs); - - for (int i = 0; i < backsub->numlines; ++i) - { - int segnum = backsub->firstline + i; - - if (segnum == window->partner) - { - continue; - } - - const MapSegGL *cseg = &Level.GLSegs[segnum]; - - if (cseg->partner == NO_INDEX) - { - continue; - } - - const WideVertex *cv1 = GetVertex (cseg->v1); - const WideVertex *cv2 = GetVertex (cseg->v2); - - if (FNodeBuilder::PointOnSide (cv1->x, cv1->y, entrance.Left->x, entrance.Left->y, wdx, wdy) <= 0 && - FNodeBuilder::PointOnSide (cv2->x, cv2->y, entrance.Left->x, entrance.Left->y, wdx, wdy) <= 0) - { - continue; - } - - fixed_t leftx = PortalStack[0].Left->x; - fixed_t lefty = PortalStack[0].Left->y; - fixed_t rightx = PortalStack[0].Right->x; - fixed_t righty = PortalStack[0].Right->y; - - fixed_t leftdx = cv1->x - leftx; - fixed_t leftdy = cv1->y - lefty; - fixed_t rightdx = rightx - cv2->x; - fixed_t rightdy = righty - cv2->y; - - if (FNodeBuilder::PointOnSide (cv1->x, cv1->y, rightx, righty, rightdx, rightdy) >= 0 || - FNodeBuilder::PointOnSide (cv2->x, cv2->y, leftx, lefty, leftdx, leftdy) >= 0) - { - continue; - } - - for (j = PortalStack.Size(); j-- > 1; ) - { - if (FNodeBuilder::PointOnSide (PortalStack[j].Left->x, PortalStack[j].Left->y, - rightx, righty, rightdx, rightdy) >= 0 || - FNodeBuilder::PointOnSide (PortalStack[j].Right->x, PortalStack[j].Right->y, - leftx, lefty, leftdx, leftdy) >= 0) - { - break; - } - } - if (j == 0) - { - TracePathDeep (cseg); - } - } - PortalStack.Pop (entrance); -} diff --git a/zdbsp_src/Unused/rejectbuilder.h.bak b/zdbsp_src/Unused/rejectbuilder.h.bak deleted file mode 100644 index 2375ef1293..0000000000 --- a/zdbsp_src/Unused/rejectbuilder.h.bak +++ /dev/null @@ -1,40 +0,0 @@ -#include "zdbsp.h" -#include "tarray.h" -#include "doomdata.h" - -class FRejectBuilder -{ -public: - FRejectBuilder (FLevel &level); - ~FRejectBuilder (); - - BYTE *GetReject (); - -private: - struct Portal - { - const MapSubsector *Subsector; - const WideVertex *Left; - const WideVertex *Right; - }; - enum ESegSeeStatus - { - MIGHT_SEE, - CAN_SEE, - CANNOT_SEE - }; - - void BuildReject (); - void TracePath (int subsector, const MapSegGL *window); - void TracePathDeep (const MapSegGL *window); - inline const WideVertex *GetVertex (WORD vertnum); - - BYTE *SubSeeMatrix; - WORD *SegSubsectors; - TArray PortalStack; - - FLevel &Level; - - int SourceRow; - int SourceSeg, SegRow; -}; diff --git a/zdbsp_src/processor.cc b/zdbsp_src/processor.cc index a3894a99a7..86e1b1931a 100644 --- a/zdbsp_src/processor.cc +++ b/zdbsp_src/processor.cc @@ -20,6 +20,7 @@ #include "processor.h" #include "rejectbuilder.h" +#include "rejectbuilder_nogl.h" enum { @@ -652,12 +653,18 @@ void FProcessor::Write (FWadWriter &out) switch (RejectMode) { + + case ERM_Rebuild_NoGL: + { + FRejectBuilderNoGL reject(Level); + Level.Reject = reject.GetReject(); + break; + } + case ERM_Rebuild: { FRejectBuilder reject(Level); Level.Reject = reject.GetReject(); - //printf (" Rebuilding the reject is unsupported.\n"); - // Intentional fall-through break; } diff --git a/zdbsp_src/rejectbuilder_nogl.cc b/zdbsp_src/rejectbuilder_nogl.cc new file mode 100644 index 0000000000..ccbfd5f05b --- /dev/null +++ b/zdbsp_src/rejectbuilder_nogl.cc @@ -0,0 +1,325 @@ +// This is the same algorithm used by DoomBSP: +// +// Represent each sector by its bounding box. Then for each pair of +// sectors, see if any chains of one-sided lines can walk from one +// side of the convex hull for that pair to the other side. +// +// It works, but it's far from being perfect. It's quite easy for +// this algorithm to consider two sectors as being visible from +// each other when they are really not. But it won't erroneously +// flag two sectors as obstructed when they're really not, and that's +// the only thing that really matters when building a REJECT lump. + +#include +#include + +#include "rejectbuilder_nogl.h" +#include "templates.h" + +FRejectBuilderNoGL::FRejectBuilderNoGL (FLevel &level) + : Level (level), BlockChains (NULL) +{ + RejectSize = (Level.NumSectors()*Level.NumSectors() + 7) / 8; + Reject = new BYTE[RejectSize]; + memset (Reject, 0, RejectSize); + + FindSectorBounds (); + FindBlockChains (); + BuildReject (); +} + +FRejectBuilderNoGL::~FRejectBuilderNoGL () +{ + FBlockChain *chain, *next; + + chain = BlockChains; + while (chain != NULL) + { + next = chain->Next; + delete chain; + chain = next; + } +} + +BYTE *FRejectBuilderNoGL::GetReject () +{ + return Reject; +} + +void FRejectBuilderNoGL::FindSectorBounds () +{ + int i; + + SectorBounds = new BBox[Level.NumSectors()]; + + for (i = 0; i < Level.NumSectors(); ++i) + { + SectorBounds[i].Bounds[LEFT] = SectorBounds[i].Bounds[BOTTOM] = INT_MAX; + SectorBounds[i].Bounds[RIGHT] = SectorBounds[i].Bounds[TOP] = INT_MIN; + } + + for (i = 0; i < Level.NumLines(); ++i) + { + if (Level.Lines[i].sidenum[0] != NO_INDEX) + { + int secnum = Level.Sides[Level.Lines[i].sidenum[0]].sector; + SectorBounds[secnum].AddPt (Level.Vertices[Level.Lines[i].v1]); + SectorBounds[secnum].AddPt (Level.Vertices[Level.Lines[i].v2]); + } + if (Level.Lines[i].sidenum[1] != NO_INDEX) + { + int secnum = Level.Sides[Level.Lines[i].sidenum[1]].sector; + SectorBounds[secnum].AddPt (Level.Vertices[Level.Lines[i].v1]); + SectorBounds[secnum].AddPt (Level.Vertices[Level.Lines[i].v2]); + } + } +} + +void FRejectBuilderNoGL::FindBlockChains () +{ + bool *marked = new bool[Level.NumLines()]; + DWORD *nextForVert = new DWORD[Level.NumLines()]; + DWORD *firstLine = new DWORD[Level.NumVertices]; + int i, j, k; + FBlockChain *chain; + FPoint pt; + TArray pts; + + memset (nextForVert, 0xff, Level.NumLines()*sizeof(*nextForVert)); + memset (firstLine, 0xff, Level.NumVertices*sizeof(*firstLine)); + memset (marked, 0, Level.NumLines()*sizeof(*marked)); + + for (i = 0; i < Level.NumLines(); ++i) + { + if (Level.Lines[i].sidenum[0] == NO_INDEX || Level.Lines[i].sidenum[1] != NO_INDEX) + { + marked[i] = true; + } + else + { + nextForVert[Level.Lines[i].v1] = firstLine[Level.Lines[i].v1]; + firstLine[Level.Lines[i].v1] = i; + } + } + + for (i = 0; i < Level.NumLines(); ++i) + { + if (marked[i]) + { + continue; + } + + pt.x = Level.Vertices[Level.Lines[i].v1].x >> FRACBITS; + pt.y = Level.Vertices[Level.Lines[i].v1].y >> FRACBITS; + pts.Clear (); + pts.Push (pt); + chain = new FBlockChain; + chain->Bounds(LEFT) = chain->Bounds(RIGHT) = pt.x; + chain->Bounds(TOP) = chain->Bounds(BOTTOM) = pt.y; + + for (j = i; j != NO_INDEX; ) + { + marked[j] = true; + pt.x = Level.Vertices[Level.Lines[j].v2].x >> FRACBITS; + pt.y = Level.Vertices[Level.Lines[j].v2].y >> FRACBITS; + pts.Push (pt); + chain->Bounds.AddPt (pt); + + k = firstLine[Level.Lines[j].v2]; + if (k == NO_INDEX) + { + break; + } + if (nextForVert[k] == NO_INDEX) + { + j = marked[k] ? NO_INDEX : k; + } + else + { + int best = NO_INDEX; + angle_t bestang = ANGLE_MAX; + angle_t ang1 = PointToAngle (Level.Vertices[Level.Lines[j].v2].x - Level.Vertices[Level.Lines[j].v1].x, + Level.Vertices[Level.Lines[j].v2].y - Level.Vertices[Level.Lines[j].v1].y) + (1 << 31); + + while (k != NO_INDEX) + { + if (!marked[k]) + { + angle_t ang2 = PointToAngle (Level.Vertices[Level.Lines[k].v2].x - Level.Vertices[Level.Lines[k].v1].x, + Level.Vertices[Level.Lines[k].v2].y - Level.Vertices[Level.Lines[k].v1].y) + (1 << 31); + angle_t angdiff = ang2 - ang1; + + if (angdiff < bestang && angdiff > 0) + { + bestang = angdiff; + best = k; + } + } + k = nextForVert[k]; + } + + j = best; + } + } + + chain->NumPoints = pts.Size(); + chain->Points = new FPoint[chain->NumPoints]; + memcpy (chain->Points, &pts[0], chain->NumPoints*sizeof(*chain->Points)); + chain->Next = BlockChains; + BlockChains = chain; + } +} + +void FRejectBuilderNoGL::HullSides (const BBox &box1, const BBox &box2, FPoint sides[4]) +{ + static const int vertSides[4][2] = { + { LEFT, BOTTOM }, + { LEFT, TOP }, + { RIGHT, TOP }, + { RIGHT, BOTTOM } + }; + static const int stuffSpots[4] = { 0, 3, 2, 1 }; + + const int *boxp1, *boxp2; + + boxp1 = box2.Bounds; + boxp2 = box1.Bounds; + + for (int mainBox = 2; mainBox != 0; ) + { + const int *stuffs = stuffSpots + (--mainBox)*2; + int outerEdges[4]; + + outerEdges[LEFT] = boxp1[LEFT] <= boxp2[LEFT]; + outerEdges[TOP] = boxp1[TOP] >= boxp2[TOP]; + outerEdges[RIGHT] = boxp1[RIGHT] >= boxp2[RIGHT]; + outerEdges[BOTTOM] = boxp1[BOTTOM] <= boxp2[BOTTOM]; + + for (int vertex = 0; vertex < 4; ++vertex) + { + if (outerEdges[(vertex-1)&3] != outerEdges[vertex]) + { + FPoint *pt = &sides[stuffs[outerEdges[vertex]]]; + pt->x = boxp1[vertSides[vertex][0]]; + pt->y = boxp1[vertSides[vertex][1]]; + } + } + + boxp1 = box1.Bounds; + boxp2 = box2.Bounds; + } +} + +int FRejectBuilderNoGL::PointOnSide (const FPoint *pt, const FPoint &lpt1, const FPoint &lpt2) +{ + return (pt->y - lpt1.y) * (lpt2.x - lpt1.x) >= (pt->x - lpt1.x) * (lpt2.y - lpt1.y); +} + +bool FRejectBuilderNoGL::ChainBlocks (const FBlockChain *chain, + const BBox *hullBounds, const FPoint *hullPts) +{ + int startSide, side, i; + + if (chain->Bounds[LEFT] > hullBounds->Bounds[RIGHT] || + chain->Bounds[RIGHT] < hullBounds->Bounds[LEFT] || + chain->Bounds[TOP] < hullBounds->Bounds[BOTTOM] || + chain->Bounds[BOTTOM] > hullBounds->Bounds[TOP]) + { + return false; + } + + startSide = -1; + + for (i = 0; i < chain->NumPoints; ++i) + { + const FPoint *pt = &chain->Points[i]; + + if (PointOnSide (pt, hullPts[1], hullPts[2])) + { + startSide = -1; + continue; + } + if (PointOnSide (pt, hullPts[3], hullPts[0])) + { + startSide = -1; + continue; + } + if (PointOnSide (pt, hullPts[0], hullPts[1])) + { + side = 0; + } + else if (PointOnSide (pt, hullPts[2], hullPts[3])) + { + side = 1; + } + else + { + continue; + } + if (startSide == -1 || startSide == side) + { + startSide = side; + } + else + { + return true; + } + } + + return false; +} + +void FRejectBuilderNoGL::BuildReject () +{ + int s1, s2; + + for (s1 = 0; s1 < Level.NumSectors()-1; ++s1) + { + printf (" Reject: %3d%%\r", s1*100/Level.NumSectors()); + for (s2 = s1 + 1; s2 < Level.NumSectors(); ++s2) + { + BBox HullBounds; + FPoint HullPts[4]; + const BBox *sb1, *sb2; + + sb1 = &SectorBounds[s1]; + sb2 = &SectorBounds[s2]; + + int pos = s1*Level.NumSectors() + s2; + if (Reject[pos>>3] & (1<<(pos&7))) + { + continue; + } + + // Overlapping and touching sectors are considered to always + // see each other. + if (sb1->Bounds[LEFT] <= sb2->Bounds[RIGHT] && + sb1->Bounds[RIGHT] >= sb2->Bounds[LEFT] && + sb1->Bounds[TOP] >= sb2->Bounds[BOTTOM] && + sb1->Bounds[BOTTOM] <= sb2->Bounds[TOP]) + { + continue; + } + + HullBounds(LEFT) = MIN (sb1->Bounds[LEFT], sb2->Bounds[LEFT]); + HullBounds(RIGHT) = MAX (sb1->Bounds[RIGHT], sb2->Bounds[RIGHT]); + HullBounds(BOTTOM) = MIN (sb1->Bounds[BOTTOM], sb2->Bounds[BOTTOM]); + HullBounds(TOP) = MAX (sb1->Bounds[TOP], sb2->Bounds[TOP]); + + HullSides (*sb1, *sb2, HullPts); + + for (FBlockChain *chain = BlockChains; chain != NULL; chain = chain->Next) + { + if (ChainBlocks (chain, &HullBounds, HullPts)) + { + break; + } + } + + Reject[pos>>3] |= 1 << (pos & 7); + pos = s2*Level.NumSectors() + s1; + Reject[pos>>3] |= 1 << (pos & 7); + } + } + printf (" Reject: 100%%\n"); +} diff --git a/zdbsp_src/rejectbuilder_nogl.h b/zdbsp_src/rejectbuilder_nogl.h new file mode 100644 index 0000000000..66f4ac9367 --- /dev/null +++ b/zdbsp_src/rejectbuilder_nogl.h @@ -0,0 +1,77 @@ +#include "zdbsp.h" +#include "tarray.h" +#include "doomdata.h" + +class FRejectBuilderNoGL +{ + struct FPoint + { + int x, y; + }; + + struct BBox + { + int Bounds[4]; + + const int &operator[] (int index) const + { + return Bounds[index]; + } + int &operator() (int index) + { + return Bounds[index]; + } + void AddPt (int x, int y) + { + if (x < Bounds[LEFT]) Bounds[LEFT] = x; + if (x > Bounds[RIGHT]) Bounds[RIGHT] = x; + if (y < Bounds[BOTTOM]) Bounds[BOTTOM] = y; + if (y > Bounds[TOP]) Bounds[TOP] = y; + } + void AddPt (WideVertex vert) + { + AddPt (vert.x >> FRACBITS, vert.y >> FRACBITS); + } + void AddPt (FPoint pt) + { + AddPt (pt.x, pt.y); + } + }; + + struct FBlockChain + { + FBlockChain() : Points(0) {} + ~FBlockChain() { if (Points) delete[] Points; } + + BBox Bounds; + FPoint *Points; + int NumPoints; + FBlockChain *Next; + }; + + enum { LEFT, TOP, RIGHT, BOTTOM }; + friend struct BBox; + +public: + FRejectBuilderNoGL (FLevel &level); + ~FRejectBuilderNoGL (); + + BYTE *GetReject (); + +private: + void FindSectorBounds (); + void FindBlockChains (); + void HullSides (const BBox &box1, const BBox &box2, FPoint sides[4]); + bool ChainBlocks (const FBlockChain *chain, const BBox *hullBounds, const FPoint *hullPts); + void BuildReject (); + + inline int PointOnSide (const FPoint *pt, const FPoint &lpt1, const FPoint &lpt2); + + BBox *SectorBounds; + BYTE *Reject; + + FLevel &Level; + int RejectSize; + + FBlockChain *BlockChains; +}; diff --git a/zdbsp_src/vis.cc b/zdbsp_src/vis.cc index 5ae726a18f..4021cdd08a 100644 --- a/zdbsp_src/vis.cc +++ b/zdbsp_src/vis.cc @@ -9,7 +9,7 @@ #include "templates.h" #include -bool FastVis=0; +bool FastVis=1; bool NoPassageVis=1; bool NoSort; diff --git a/zdbsp_src/zdbsp.h b/zdbsp_src/zdbsp.h index 36ef78dca5..5b656201ef 100644 --- a/zdbsp_src/zdbsp.h +++ b/zdbsp_src/zdbsp.h @@ -29,7 +29,8 @@ enum ERejectMode ERM_DontTouch, ERM_CreateZeroes, ERM_Create0, - ERM_Rebuild + ERM_Rebuild, + ERM_Rebuild_NoGL }; extern const char *Map; From 80b47a01345ea43bf407b0ac2efa4093bd0be734 Mon Sep 17 00:00:00 2001 From: dashodanger Date: Sun, 21 Mar 2021 13:07:00 +0430 Subject: [PATCH 14/20] Cleanup of some nodebuilder-related code --- gui/g_doom.cc | 79 +++++++++++++++++++++------------------------------ 1 file changed, 33 insertions(+), 46 deletions(-) diff --git a/gui/g_doom.cc b/gui/g_doom.cc index 85a35ba1a3..d17f98956d 100644 --- a/gui/g_doom.cc +++ b/gui/g_doom.cc @@ -844,42 +844,30 @@ static bool DM_BuildNodes(const char *filename, const char *out_name) { LogPrintf("\n"); - // Node building and map format options are a moot point for non-ZDoom engines - - if (current_engine != "zdoom") + zdbsp_options options; + if (current_engine == "nolimit") { - zdbsp_options options; - if (current_engine == "nolimit") - { - options.build_nodes = true; - options.build_gl_nodes = false; - options.build_gl_only = false; - options.reject_mode = ERM_CreateZeroes; - options.check_polyobjs = false; - options.compress_nodes = false; - options.compress_gl_nodes = false; - options.force_compression = false; - } - else if (current_engine == "boom") - { - options.build_nodes = true; - options.build_gl_nodes = true; - options.build_gl_only = true; - options.reject_mode = ERM_Rebuild; - options.check_polyobjs = false; - options.compress_nodes = true; - options.compress_gl_nodes = false; - options.force_compression = false; - } - if (zdmain(filename, options) != 0) - { - Main_ProgStatus(_("ZDBSP Error!")); - return false; - } - FileRename(filename, out_name); - return true; - } - else + options.build_nodes = true; + options.build_gl_nodes = false; + options.build_gl_only = false; + options.reject_mode = ERM_CreateZeroes; + options.check_polyobjs = false; + options.compress_nodes = false; + options.compress_gl_nodes = false; + options.force_compression = false; + } + else if (current_engine == "boom") + { + options.build_nodes = true; + options.build_gl_nodes = true; + options.build_gl_only = true; + options.reject_mode = ERM_Rebuild; + options.check_polyobjs = false; + options.compress_nodes = true; + options.compress_gl_nodes = false; + options.force_compression = false; + } + else if (current_engine == "zdoom") { if (build_nodes == "no") { @@ -887,7 +875,6 @@ static bool DM_BuildNodes(const char *filename, const char *out_name) FileRename(filename, out_name); return true; } - zdbsp_options options; options.build_nodes = true; options.build_gl_nodes = true; options.build_gl_only = true; @@ -895,17 +882,17 @@ static bool DM_BuildNodes(const char *filename, const char *out_name) options.check_polyobjs = true; options.compress_nodes = true; options.compress_gl_nodes = true; - options.force_compression = true; - if (zdmain(filename, options) != 0) - { - Main_ProgStatus(_("ZDBSP Error!")); - return false; - } - FileRename(filename, out_name); - return true; + options.force_compression = true; } - // This shouldn't be reached, so return false if it is - return false; + + if (zdmain(filename, options) != 0) + { + Main_ProgStatus(_("ZDBSP Error!")); + return false; + } + + FileRename(filename, out_name); + return true; } From 6fb99d8639020f07fd6cd39d309b63b5c9c476ae Mon Sep 17 00:00:00 2001 From: dashodanger Date: Sun, 21 Mar 2021 13:25:39 +0430 Subject: [PATCH 15/20] Added toggles for REJECT lump building --- gui/g_doom.cc | 36 ++++++++++++++++++++++--- modules/ui_reject_options.lua | 46 ++++++++++++++++++++++++++++++++ modules/ui_zdoom_map_options.lua | 13 ++++++--- 3 files changed, 89 insertions(+), 6 deletions(-) create mode 100644 modules/ui_reject_options.lua diff --git a/gui/g_doom.cc b/gui/g_doom.cc index d17f98956d..e632244075 100644 --- a/gui/g_doom.cc +++ b/gui/g_doom.cc @@ -72,6 +72,7 @@ static int errors_seen; std::string current_engine; std::string map_format; std::string build_nodes; +std::string build_reject; static bool UDMF_mode; @@ -850,7 +851,14 @@ static bool DM_BuildNodes(const char *filename, const char *out_name) options.build_nodes = true; options.build_gl_nodes = false; options.build_gl_only = false; - options.reject_mode = ERM_CreateZeroes; + if (build_reject == "yes") + { + options.reject_mode = ERM_Rebuild_NoGL; + } + else + { + options.reject_mode = ERM_CreateZeroes; + } options.check_polyobjs = false; options.compress_nodes = false; options.compress_gl_nodes = false; @@ -861,7 +869,14 @@ static bool DM_BuildNodes(const char *filename, const char *out_name) options.build_nodes = true; options.build_gl_nodes = true; options.build_gl_only = true; - options.reject_mode = ERM_Rebuild; + if (build_reject == "yes") + { + options.reject_mode = ERM_Rebuild; + } + else + { + options.reject_mode = ERM_CreateZeroes; + } options.check_polyobjs = false; options.compress_nodes = true; options.compress_gl_nodes = false; @@ -878,7 +893,14 @@ static bool DM_BuildNodes(const char *filename, const char *out_name) options.build_nodes = true; options.build_gl_nodes = true; options.build_gl_only = true; - options.reject_mode = ERM_Rebuild; + if (build_reject == "yes") + { + options.reject_mode = ERM_Rebuild; + } + else + { + options.reject_mode = ERM_DontTouch; + } options.check_polyobjs = true; options.compress_nodes = true; options.compress_gl_nodes = true; @@ -956,6 +978,14 @@ bool doom_game_interface_c::Start(const char *preset) main_win->build_box->Prog_Init(20, N_("CSG")); current_engine = main_win->game_box->engine->GetID(); + if (current_engine == "zdoom") + { + build_reject = main_win->left_mods->FindID("ui_zdoom_map_options")->FindOpt("build_reject")->GetID(); + } + else + { + build_reject = main_win->left_mods->FindID("ui_reject_options")->FindOpt("build_reject")->GetID(); + } map_format = main_win->left_mods->FindID("ui_zdoom_map_options")->FindOpt("map_format")->GetID(); build_nodes = main_win->left_mods->FindID("ui_zdoom_map_options")->FindOpt("build_nodes")->GetID(); if (current_engine == "zdoom" && map_format == "udmf") diff --git a/modules/ui_reject_options.lua b/modules/ui_reject_options.lua new file mode 100644 index 0000000000..897f895018 --- /dev/null +++ b/modules/ui_reject_options.lua @@ -0,0 +1,46 @@ +------------------------------------------------------------------------ +-- PANEL: REJECT Builder Options +------------------------------------------------------------------------ +-- +-- Copyright (C) 2021 Dashodanger +-- +-- This program is free software; you can redistribute it and/or +-- modify it under the terms of the GNU General Public License +-- as published by the Free Software Foundation; either version 2, +-- of the License, or (at your option) any later version. +-- +-- This program is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU General Public License for more details. +-- +------------------------------------------------------------------------ + +UI_REJECT_OPTIONS = { } + +UI_REJECT_OPTIONS.YES_NO = +{ + "yes", _("Yes"), + "no", _("No"), +} + +OB_MODULES["ui_reject_options"] = +{ + label = _("Map Build Options"), + + engine = "!zdoom", + + side = "left", + priority = 105, + + options = + { + { + name = "build_reject", + label = _("Build REJECT"), + choices = UI_REJECT_OPTIONS.YES_NO, + default = "no", + tooltip = "Choose to build a proper REJECT lump. WARNING: This can be very time consuming!", + } + } +} diff --git a/modules/ui_zdoom_map_options.lua b/modules/ui_zdoom_map_options.lua index 2094f32e0f..d8494903c1 100644 --- a/modules/ui_zdoom_map_options.lua +++ b/modules/ui_zdoom_map_options.lua @@ -46,15 +46,22 @@ OB_MODULES["ui_zdoom_map_options"] = label = _("Build Nodes"), choices = UI_ZDOOM_MAP_OPTIONS.YES_NO, default = "no", - tooltip = "For ZDoom based engines, choose to either build nodes or allow the engine itself to do so " .. - "upon loading the map. No effect on other engines.", + tooltip = "Choose to either build nodes or allow the engine itself to do so " .. + "upon loading the map.", }, { name = "map_format", label = _("Map Format"), choices = UI_ZDOOM_MAP_OPTIONS.MAP_FORMAT_CHOICES, default = "udmf", - tooltip = "For ZDoom based engines, choose between UDMF and binary map format. No effect on other engines.", + tooltip = "Choose between UDMF and binary map format.", + }, + { + name = "build_reject", + label = _("Build REJECT"), + choices = UI_ZDOOM_MAP_OPTIONS.YES_NO, + default = "no", + tooltip = "Choose to build a proper REJECT lump (Binary map format only). WARNING: This can be very time consuming!", } } } From 8ab1fc21128288bbcc15a6ed869f643d861b8a77 Mon Sep 17 00:00:00 2001 From: dashodanger Date: Sun, 21 Mar 2021 14:20:10 +0430 Subject: [PATCH 16/20] Updated version number and Windows makefiles --- Makefile_win32.xming | 6 +++++- Makefile_win64.xming | 4 ++++ gui/main.h | 4 ++-- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/Makefile_win32.xming b/Makefile_win32.xming index 265a116e71..e935b471e8 100644 --- a/Makefile_win32.xming +++ b/Makefile_win32.xming @@ -164,11 +164,15 @@ ZDBSP_OBJS= \ $(OBJ_DIR)/zdbsp/sc_man.o \ $(OBJ_DIR)/zdbsp/zdwad.o \ $(OBJ_DIR)/zdbsp/nodebuild.o \ + $(OBJ_DIR)/zdbsp/rejectbuilder.o \ + $(OBJ_DIR)/zdbsp/rejectbuilder_nogl.o \ + $(OBJ_DIR)/zdbsp/vis.o \ + $(OBJ_DIR)/zdbsp/visflow.o \ $(OBJ_DIR)/zdbsp/nodebuild_events.o \ $(OBJ_DIR)/zdbsp/nodebuild_extract.o \ $(OBJ_DIR)/zdbsp/nodebuild_gl.o \ $(OBJ_DIR)/zdbsp/nodebuild_utility.o \ - $(OBJ_DIR)/zdbsp/nodebuild_classify_nosse2.o + $(OBJ_DIR)/zdbsp/nodebuild_classify_nosse2.o ZDBSP_CXXFLAGS=$(OPTIMISE) -Wall -DINLINE_G=inline diff --git a/Makefile_win64.xming b/Makefile_win64.xming index 4038b89e5a..886e1ced75 100644 --- a/Makefile_win64.xming +++ b/Makefile_win64.xming @@ -164,6 +164,10 @@ ZDBSP_OBJS= \ $(OBJ_DIR)/zdbsp/sc_man.o \ $(OBJ_DIR)/zdbsp/zdwad.o \ $(OBJ_DIR)/zdbsp/nodebuild.o \ + $(OBJ_DIR)/zdbsp/rejectbuilder.o \ + $(OBJ_DIR)/zdbsp/rejectbuilder_nogl.o \ + $(OBJ_DIR)/zdbsp/vis.o \ + $(OBJ_DIR)/zdbsp/visflow.o \ $(OBJ_DIR)/zdbsp/nodebuild_events.o \ $(OBJ_DIR)/zdbsp/nodebuild_extract.o \ $(OBJ_DIR)/zdbsp/nodebuild_gl.o \ diff --git a/gui/main.h b/gui/main.h index 5d75c4dda6..0e0127c8d6 100644 --- a/gui/main.h +++ b/gui/main.h @@ -23,8 +23,8 @@ #define OBSIDIAN_TITLE "OBSIDIAN Level Maker" -#define OBSIDIAN_VERSION "Beta 10" -#define OBSIDIAN_HEX_VER 0x00A +#define OBSIDIAN_VERSION "Beta 11" +#define OBSIDIAN_HEX_VER 0x00B #define OBSIDIAN_WEBSITE "https://github.com/GTD-Carthage/Oblige" From d0fc4b036a5f2502df75855f2a62f9b4958ef221 Mon Sep 17 00:00:00 2001 From: dashodanger Date: Sun, 21 Mar 2021 15:04:23 +0430 Subject: [PATCH 17/20] Fixed Level seed displaying in wrong notation --- scripts/level.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/level.lua b/scripts/level.lua index 421d0e55bb..3a596b9024 100644 --- a/scripts/level.lua +++ b/scripts/level.lua @@ -2836,7 +2836,7 @@ function Level_make_level(LEV) gui.printf("\n\n~~~~~~| %s |~~~~~~\n", LEVEL.name) LEVEL.seed = gui.random_int() - gui.printf("Level seed: " .. LEVEL.seed .. "\n") + gui.printf("Level seed: %u\n", LEVEL.seed) LEVEL.ids = {} THEME = table.copy(assert(LEVEL.theme)) From 2dc03da04a68aef1689714abab381059e8da195f Mon Sep 17 00:00:00 2001 From: "Tobby \"GTD-Carthage\" Ong" Date: Mon, 22 Mar 2021 11:21:43 +0800 Subject: [PATCH 18/20] - Fixed minor alignment issues in guardpost cage fabs. --- .../cage/scionox_cage_guardpost_destroyed.lua | 5 ++--- .../fabs/item/scionox_guardpost_blocked.lua | 6 ++---- .../fabs/item/scionox_guardpost_blocked.wad | Bin 26594 -> 26598 bytes 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/games/doom/fabs/cage/scionox_cage_guardpost_destroyed.lua b/games/doom/fabs/cage/scionox_cage_guardpost_destroyed.lua index e2cdd8d2a9..784d8fa5ec 100644 --- a/games/doom/fabs/cage/scionox_cage_guardpost_destroyed.lua +++ b/games/doom/fabs/cage/scionox_cage_guardpost_destroyed.lua @@ -13,12 +13,11 @@ PREFABS.Cage_scionox_guardpost_destroyed = where = "seeds", shape = "U", + deep = 16, + seed_w = 2, seed_h = 2, - deep = 16, - over = 16, - x_fit = "frame", y_fit = "frame", diff --git a/games/doom/fabs/item/scionox_guardpost_blocked.lua b/games/doom/fabs/item/scionox_guardpost_blocked.lua index 5a88e6f834..67a4da89fa 100644 --- a/games/doom/fabs/item/scionox_guardpost_blocked.lua +++ b/games/doom/fabs/item/scionox_guardpost_blocked.lua @@ -12,11 +12,11 @@ PREFABS.Item_scionox_guardpost_blocked = where = "seeds", + deep = 16, + seed_w = 2, seed_h = 2, - deep = 16, - x_fit = "frame", y_fit = "frame", @@ -47,8 +47,6 @@ PREFABS.Item_scionox_guardpost_secret = seed_w = 3, seed_h = 2, - deep = 16, - x_fit = "frame", y_fit = "frame", diff --git a/games/doom/fabs/item/scionox_guardpost_blocked.wad b/games/doom/fabs/item/scionox_guardpost_blocked.wad index e09cad389d43bf7676f69ae830b1a1106871a645..072c0bdf39f1e986ed68dfe97b94b8483b2dc239 100644 GIT binary patch delta 1102 zcmYjQO-NKx6#njvQ==7_nt!9@G|pH}Hj1Q3zot(!f5zsGIyL@AVCtfFEn0XXFe518 zAqi1xEuvgRJX%CWvxycFwkR+NB%^4Tt&%p=cU~@fk9W>J=X~dU=bn4t>XQ1tq%ymD z8=CR~Og_ncSFlZ|H(sTcw!aqCsA`>_g;ul3f~Mw|*2t!oXPK6K39b{=;?s6Z(PpKV zt!VKjrL8x`5tXoi7hmr{qV1|4JzB(q$-Q zsbeHapp3-S)=K$E(CLmo(y$G;&cHmabrK6we3aIBu&7k4Y(*5Z^cJnLj%?9J>)l9eR=EY5?ak z=dAAGA)a#e3@@EGWi9R>Jsd|jdN72uNOQj#$Z@pSkuC(eix~RQ&pLySFN-tZjy&XZ z=6UMf&(z@kCDed569!u_O-U8jNw~*O{P{u z+#)~A-x2mqaa!6w)!}wA+#QV+rP$aQ?~IFq6vi=3waEe9i9T>EuWgtfFR^V&A4`E3 zob{~>NuNwM8KHchT%o+(C$oOlC&zxdU$9f8C7CtXKj)@pHw=tQPm6;D+Dn7uVjLXi z*{Br9qhX+#dX3!gH%@?FUIB^KqlFR*yk_F#n!CI?e9JR2Md$JMXk%MPL)V`Ov-xmY zRZT7SA?o;v*3K3asIB4;$KAEJb{b7avw5Qy_?!ortxb+S)&`Vt5# z#ke8az#0BR`^aaSMq@l0Gbc~uFba7CJEKj8AntN}Tiuf2#PR5yqyaJGBt3BK&;K?2 E7fc{HPyhe` delta 1120 zcmYjQ&udgy6#mYPQzxl~@mFFphGv{hjE;7qQYBz(t$C(tevF+LO=J@DBBep|2ei9f z(n3l_8n}pqUHF2fU34MkEkq*qfk+o37TQGuLa2qJD|MsUjo-Os;d{J$?m6c>=R4=z z`~H6r_G#{k82ez?w*-{)zAQvH z^6yLAs7oOxVcM~SsXigW7}`m^j8VJVFy6>gvLk*IvrAZLsn43 z9S$+KXI2PY+HHmBf*t zoHGM#SAMEV=g&``ZT>_6PLW+35!MrZVl;1jaFe zbC`5mBj>ys+t6gcD0wNLnNQoFO1V7+_P8J2sI21zTd^HYXy$q3u#3JDK7@x#v{Jg6 zO~ryl99MGak<9V+4#+SIx zPGnikyt_EDy)kh;NiFiuUC>HzP7)aXJvpPwv8ilN%zObedN@h>f&_onW8uZpRw&ii zgFfZtWlcMq#eSlK+qm(0`^$+*?z`<_*g6w&) zf7t8wPWnF_1|Dw%{Qh32e>DK?a>buJ mi;u8_cXGJg>nY+Ihq@`L0nU$KzSJ Date: Mon, 22 Mar 2021 11:22:05 +0800 Subject: [PATCH 19/20] - Added old city skybox fab to tech theme but with lower prob. --- games/doom/themes.lua | 2 +- modules/zdoom_armaetus_themes.lua | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/games/doom/themes.lua b/games/doom/themes.lua index 75a696f608..dd8312b75b 100644 --- a/games/doom/themes.lua +++ b/games/doom/themes.lua @@ -1834,7 +1834,7 @@ DOOM.THEMES = { Skybox_hellish_city = 50, Skybox_garrett_city = 50 - }, + } }, ------------------------------------ diff --git a/modules/zdoom_armaetus_themes.lua b/modules/zdoom_armaetus_themes.lua index f0f0cc4363..e712bfaa5e 100644 --- a/modules/zdoom_armaetus_themes.lua +++ b/modules/zdoom_armaetus_themes.lua @@ -3451,6 +3451,7 @@ ARMAETUS_THEMES = skyboxes = { + Skybox_garrett_city_EPIC = 25, Skybox_tech_ffvii_EPIC = 50, Skybox_generic_EPIC = 50, Skybox_craneo_fishing_village_EPIC = 25, From 7aeef64650357ba76cb4594cbb389bb9c5e41912 Mon Sep 17 00:00:00 2001 From: "Tobby \"GTD-Carthage\" Ong" Date: Mon, 22 Mar 2021 11:22:25 +0800 Subject: [PATCH 20/20] - Autodetail now defaults to off when UDMF is the selected map format. --- modules/prefab_controller.lua | 9 +++++++-- scripts/autodetail.lua | 9 +++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/modules/prefab_controller.lua b/modules/prefab_controller.lua index 2f9ef031bb..41451e1712 100644 --- a/modules/prefab_controller.lua +++ b/modules/prefab_controller.lua @@ -166,7 +166,8 @@ OB_MODULES["prefab_control"] = name = "autodetail", label=("Auto Detailing"), choices=PREFAB_CONTROL.ON_OFF, - tooltip = "Reduces the amount of complex architecture in a map based on its size. Default is on.", + tooltip = "Reduces the amount of complex architecture in a map based on its size. " .. + "Default is on in binary map format, off in UDMF map format.", default = "on", priority = 102, gap = 1 @@ -177,7 +178,11 @@ OB_MODULES["prefab_control"] = name = "point_prob", label=_("Point Decor"), choices=PREFAB_CONTROL.POINT_CHOICES, - tooltip = "Decor prefabs are prefabs placed along the floors such as crates, pillars, and other decorative elements which aren't tied to walls. This directly modifies probabilities on a per-room basis, not the density for decor prefabs in any given room.\n\nNote: DEFAULT actually behaves like Mix-It-Up.", + tooltip = "Decor prefabs are prefabs placed along the floors such as " .. + "crates, pillars, and other decorative elements which aren't tied to walls. " .. + "This directly modifies probabilities on a per-room basis, " .. + "not the density for decor prefabs in any given room. " .. + "\n\nNote: DEFAULT actually behaves like Mix-It-Up.", default = "fab_default", priority = 101 }, diff --git a/scripts/autodetail.lua b/scripts/autodetail.lua index 741187c2df..4de096f994 100644 --- a/scripts/autodetail.lua +++ b/scripts/autodetail.lua @@ -43,6 +43,9 @@ function Autodetail_get_level_svolume() LEVEL.autodetail_group_walls_factor = 1 if PARAM.autodetail == "off" then return end + if not PARAM.autodetail + and OB_CONFIG.map_format + and OB_CONFIG.map_format == "udmf" then return end local total_walkable_area = 0 @@ -63,6 +66,9 @@ function Autodetail_plain_walls() LEVEL.autodetail_plain_walls_factor = 0 if PARAM.autodetail == "off" then return end + if not PARAM.autodetail + and OB_CONFIG.map_format + and OB_CONFIG.map_format == "udmf" then return end local total_perimeter = 0 for _,junc in pairs(LEVEL.junctions) do @@ -100,6 +106,9 @@ end function Autodetail_report() if PARAM.autodetail == "off" then return end + if not PARAM.autodetail + and OB_CONFIG.map_format + and OB_CONFIG.map_format == "udmf" then return end gui.printf("\n--==| Auto Detail Report |==--\n\n") gui.printf("Total walkable volume: " .. LEVEL.total_svolume .. "\n")