--[[ Usage: in Mapster32, > lua "shadexfog=reload'shadexfog'" -- for example > lua "shadexfog.create(100, 63,63,63)" > lua "shadexfog.translate(100, 2)" In EDuke32, simply pass this module at the command line. --]] local assert = assert local error = error local print = print local printf = printf local tonumber = tonumber local type = type local unpack = unpack local bit = require("bit") local math = require("math") local string = require("string") local min, max = math.min, math.max local floor = math.floor local sector, wall, sprite = sector, wall, sprite local engine = require("engine") local gv = gv ---------- local shadexfog = {} -- Example: -- lua "shadexfog.createremap(30, {[2]=0, [3]=1, [12]=0, [13]=1})" -- creates a pal 30 which maps the blue and orange ramps to the gray ones. -- (Compare with the rows of http://wiki.eduke32.com/wiki/File:Pala.png) -- -- Sexdecatuple remappings of Duke3D pals loaded from LOOKUP.DAT: -- Remappings that are not expressible as such and identity maps (pal 3 and 9) -- omitted. -- -- 2: { [0]=8, [1]=13, [2]=8, [3]=13, [4]=13, [5]=8, [6]=8, [7]=13, [9]=8, [10]=8, [11]=13, [12]=8, [14]=8, } -- 5: { [8]=2, [13]=3, } -- 7: { [0]=10, [1]=9, [2]=10, [3]=9, [4]=9, [5]=10, [6]=10, [7]=9, [8]=10, [11]=9, [12]=9, [13]=9, [14]=9, } -- 8: { [0]=6, [1]=7, [2]=6, [3]=7, [4]=7, [5]=6, [8]=6, [9]=7, [10]=6, [11]=7, [12]=7, [13]=7, [14]=6, } -- 11: { [4]=7, [5]=6, } -- 12: { [4]=1, [5]=0, } -- 15: { [4]=3, [5]=2, } -- 17: { [2]=5, [3]=4, [4]=7, [5]=6, [6]=5, [7]=4, [12]=5, [14]=4, } -- 18: { [4]=1, [5]=0, } -- 19: { [2]=8, [3]=13, [4]=1, [5]=0, [6]=8, [7]=13, [12]=8, [14]=13, } -- 20: { [2]=5, [3]=4, [4]=1, [5]=0, [6]=5, [7]=4, [12]=5, [14]=4, } -- 21: { [4]=13, [5]=8, } -- 22: { [4]=7, [5]=6, } -- 25: { [6]=8, [7]=13, } function shadexfog.createremap(palnum, remaptab) local sht = engine.getshadetab(0) engine.setshadetab(palnum, sht:remap16(remaptab)) end -- Create 32 palookups corrensponding to different *shade levels* of a fog -- palookup, called a "shade-x-fog" palookup set in the following. -- -- Pals .. +31 will be taken. -- , , : intensities of the fog color, [0 .. 63] function shadexfog.create(startpalnum, fogr, fogg, fogb) local MAXPALNUM = 255-31-engine.RESERVEDPALS if (not (startpalnum >= 1 and startpalnum <= MAXPALNUM)) then error("invalid startpalnum, max="..MAXPALNUM, 2) end local basesht = engine.getshadetab(0) -- Encode the shade in different pal numbers! The shade tables are -- constructed with a fog in their place. for dummyshade=0,31 do local sht = engine.shadetab() for f=0,31 do for i=0,255 do local r, g, b = engine.getrgb(basesht[dummyshade][i]) local nr, ng, nb = (r*(32-f) + fogr*f) / 32, (g*(32-f) + fogg*f) / 32, (b*(32-f) + fogb*f) / 32 sht[f][i] = engine.nearcolor(nr, ng, nb) end end engine.setshadetab(startpalnum + dummyshade, sht) end end local function trans(what, startpalnum, fogintensity) if (what.pal >= startpalnum and what.pal <= startpalnum+31) then -- Auto-detect earlier translation with the same . what.shade = what.pal - startpalnum end local shade = min(max(what.shade, 0), 31) what.pal = startpalnum + shade what.shade = fogintensity end -- shadexfog.translate(startpalnum, fogintensity [, vis]) -- -- Translate the whole map for use with a shade-x-fog palookup set. -- .pal becomes the + former .shade -- .shade becomes the [0 .. 31] -- If is passed and >= 0, set all sector's visibility to that value. -- -- Notes: -- - auto-detects when the translation has been applied with the *same* -- (if a different one is desired, must reload map). -- - if shades < 0 or > 31 present, loss of information function shadexfog.translate(startpalnum, fogintensity, vis) for i=0,gv.numsectors-1 do trans(sector[i].ceiling, startpalnum, fogintensity) trans(sector[i].floor, startpalnum, fogintensity) if (vis and vis >= 0) then sector[i].visibility = vis end end for i=0,gv.numwalls-1 do trans(wall[i], startpalnum, fogintensity) end end if (gv.LUNATIC_CLIENT == gv.LUNATIC_CLIENT_EDUKE32 and LUNATIC_FIRST_TIME) then shadexfog.create(100, 63,63,63) print("created shadexfog palookups") end ---------- BASE SHADE TABLE TESTS ---------- -- sht = shadexfog.create_depth_shtab([palnum]) function shadexfog.create_depth_shtab(palnum) local sht = engine.shadetab() for s=0,31 do for i=0,255 do sht[s][i] = s end end if (palnum) then engine.setshadetab(palnum, sht) end return sht end function shadexfog.create_vismarker_shtab(palnum) local sht = engine.getshadetab(0) for i=0,255 do sht[1][i] = 242 sht[30][i] = 245 end if (palnum) then engine.setshadetab(palnum, sht) end return sht end -- Basic test of whether for a color index i corresponding to a color (r,g,b), -- getclosestcol() returns a color index ii corresponding to the same color. -- (In the Duke3D palette, there are duplicates, so the requirement i==ii is -- too strict.) function shadexfog.test_nearcolor() for i=0,255 do local r, g, b = engine.getrgb(i) local ii = engine.nearcolor(r, g, b) local rr, gg, bb = engine.getrgb(ii) if (r~=rr or g~=gg or b~=bb) then printf("diff %d: %d,%d,%d %d,%d,%d", i, r,g,b, rr,gg,bb) end end end -- Change the .pal member of all sector ceilings/floors, walls and sprites to -- . function shadexfog.challpal(palnum) for i=0,gv.numsectors-1 do sector[i].ceilingpal = palnum sector[i].floorpal = palnum end for i=0,gv.numwalls-1 do wall[i].pal = palnum end for i in sprite.all() do sprite[i].pal = palnum end end -- Create our version of the base shade table (palookup 0) -- -- NOTE: Nope, the base shade table is NOT created by applying a linear ramp to -- the base palette colors!!! local function create_base_shtab(basesht) local basesht = basesht or engine.getshadetab(0) local sht = engine.shadetab() sht[0] = basesht[0] for sh=1,31 do for i=0,255-16 do -- NOTE that this fails, see BASESHT_0_NOT_IDENTITY: -- assert(basesht[0][i] == i) local r, g, b = engine.getrgb(i) local f = 1 r = ((32-f*sh+0.5)*r)/32 g = ((32-f*sh+0.5)*g)/32 b = ((32-f*sh+0.5)*b)/32 r, g, b = max(0,r), max(0,g), max(0,b) -- if f is > 1 sht[sh][i] = engine.nearcolor(r, g, b) end for i=255-16+1,255 do -- fullbrights sht[sh][i] = basesht[0][i] end end return sht end local function create_base_shtab_2(basesht) local basesht = basesht or engine.getshadetab(0) local perm16 = { [0]=0,1, 2,3, 5,4, 6,7, 8,13, 10,11, 12,9, 14,15 } basesht = basesht:remap16(perm16) local iperm16 = {} for i=0,15 do iperm16[perm16[i]] = i end local iperm = {} for i=0,255 do iperm[i] = 16*(iperm16[floor(i/16)]) + i%16 end local baseidx = {} for i=0,255-16 do baseidx[i] = i < 192 and 32*floor(i/32) or 16*floor(i/16) end local sht = engine.shadetab() for sh=0,31 do for i=0,255-16 do local bi = baseidx[i] local cidx = bi + floor(((31-sh)*(i - bi))/31) sht[sh][i] = iperm[cidx] end for i=255-16+1,255 do -- fullbrights sht[sh][i] = basesht[0][i] end end return sht:remap16(iperm16) end if (gv.LUNATIC_CLIENT == gv.LUNATIC_CLIENT_MAPSTER32) then -- Wrapper around engine.savePaletteDat() that errors on unexpected events. function shadexfog.save(filename, palnum, blendnum, moreblends, lognumalphatabs) local ok, errmsg, nummoreblends = engine.savePaletteDat( filename, palnum, blendnum, moreblends, lognumalphatabs) if (not ok) then error(errmsg) end printf('Wrote base palette, shade and translucency tables to "%s".', filename) if (nummoreblends > 0) then printf(" Also wrote %d additional translucency tables.", nummoreblends) end end function shadexfog.saveLookupDat(filename, lookups) if (lookups == nil) then lookups = { -- Duke3D 1.5 LOOKUP.DAT order 1,2,6,7,8, 3,4,5,9,10, 12,13,15,16,18, 19,11,14,17,20, 21,22,23,24,25 } end assert(engine.saveLookupDat(filename, lookups)) printf('Wrote lookup tables and 5 base palettes to "%s".', filename) end end -- Create our (failed) version of the base shade table at set it to palookup -- number . -- : use second attempt? function shadexfog.create0(palnum, secver) local sht0 = secver and create_base_shtab_2() or create_base_shtab() engine.setshadetab(palnum, sht0) end function shadexfog.test_create0() local basesht = engine.getshadetab(0) for i=0,255 do if (basesht[0][i] ~= i) then -- BASESHT_0_NOT_IDENTITY printf("Base shade table at index %d: %d", i, basesht[0][i]) end end local sht = create_base_shtab(basesht) local ok = true for sh=1,31 do for i=0,255 do local ouri, origi = sht[sh][i], basesht[sh][i] -- if (sht[sh][i] ~= basesht[sh][i]) then if (math.abs(ouri - origi) > 1) then printf("Constructed shade table DIFFERS AT shade %d index %d: orig %d ours %d", sh, i, basesht[sh][i], sht[sh][i]) ok = false goto out end end end ::out:: if (ok) then printf("Constructed shade table IDENTICAL WITH original one") end end ---------- Blending table tests ---------- -- shadexfog.create_trans(startblendidx, func [, numtables [, fullbrightsOK]]) -- -- : must be -- rr, gg, bb = f(r, g, b, R, G, B, level, numtables) -- ('level' is the table index, from 1 to ) -- : number of tables to create, from on. Default: 1 function shadexfog.create_trans(startblendidx, func, numtables, fullbrightsOK) numtables = numtables or 1 local lastokcol = fullbrightsOK and 255 or 255-16 local tab = engine.blendtab() for level=1,numtables do for i=0,255 do local r,g,b = engine.getrgb(i) for j=0,255 do local R,G,B = engine.getrgb(j) local rr, gg, bb = func(r,g,b, R,G,B, level, numtables) tab[i][j] = engine.nearcolor(rr, gg, bb, lastokcol) end end engine.setblendtab(startblendidx + level-1, tab) end end local function check_numtables(numtables) if (numtables ~= nil) then if (type(numtables) ~= "number" or not (numtables >= 1 and numtables <= 128)) then error("invalid argument #2: must be a number in [1 .. 128]", 2) end if (bit.band(numtables, numtables-1) ~= 0) then error("invalid argument #2: must be a power of two", 2) end end end -- shadexfog.create_alpha_trans(startblendidx [, numtables [, fullbrightsOK]]) -- -- Creates blending tables of smooth alpha translucency, with -- fractions 1/(2*numtables), 2/(2*numtables) ... numtables/(2*numtables). -- must be a power of two in [1 .. 128]. function shadexfog.create_alpha_trans(startblendidx, numtables, fullbrightsOK) check_numtables(numtables) shadexfog.create_trans( startblendidx, function(r,g,b, R,G,B, alpha, numtabs) local f = alpha/(2*numtabs) local F = 1-f return f*r+F*R, f*g+F*G, f*b+F*B end, numtables, fullbrightsOK ) end -- shadexfog.create_additive_trans(startblendidx [, numtables [, fullbrightsOK]]) function shadexfog.create_additive_trans(startblendidx, numtables, fullbrightsOK) shadexfog.create_trans( startblendidx, function(r,g,b, R,G,B, level, numtabs) local f = level/numtabs return min(f*r+R, 63), min(f*g+G, 63), min(f*b+B, 63) end, numtables, fullbrightsOK ) end --========== Mapster32 Lua menu hooks ==========-- local getnumber16 = engine.getnumber16 local GNF = engine.GETNUMFLAG local GNF_BOOL = GNF.NEXTFREE local df = GNF.RET_M1_ON_CANCEL -- default getnumber16() flags local MAXUSERPALOOKUP = 256-1-8 -- KEEPINSYNC engine.lua:check_palidx() -- wrapped_func = CreateMenuFunction(argdesc) -- -- : table with [0]= and then, entries { name, init, max [, noret] } local function CreateMenuFunction(argdesc) return function() local func = argdesc[0] assert(type(func) == "function") local args = {} for i=1,#argdesc do local ad = argdesc[i] assert(type(ad) == "table" and #ad == 3 or #ad == 4) local moreflags = ad[4] or 0 args[i] = getnumber16(ad[1]..": ", ad[2], ad[3], bit.bor(df, moreflags)) if (bit.band(moreflags, GNF.NEG_ALLOWED)==0 and args[i] < 0) then return end if (bit.band(moreflags, GNF_BOOL)~=0) then args[i] = (args[i] > 0) end end func(unpack(args)) end end -- Replace chevrons (angle brackets!) with printext16 markup. local function replchev(matchstr) return "^15"..matchstr:sub(2,-2).."^O" end -- Replace ASCII code escapes like \XXX. We can't use these escapes in Lua [[ ... ]] strings. local function replascii(matchstr) return string.char(tonumber(matchstr)) end -- Format a whole string for the menu system: local function formatHelp(str) return str:gsub( "(%b<>)", replchev):gsub( "%*(.-)%*", "^014%1^O"):gsub( "%\\(%d+)", replascii) end ---------- engine.clearMenu() engine.registerMenuFunc( "Create shadexfog palset", CreateMenuFunction{ [0] = shadexfog.create, { "Starting palnum", 100, MAXUSERPALOOKUP-31 }, { "Red fog color [0-63]", 0, 63 }, { "Green fog color [0-63]", 0, 63 }, { "Blue fog color [0-63]", 0, 63 }, }, formatHelp [[ <_______________________________________________> Creates 32 shade tables corresponding to different *shade levels* of a fog palookup, together called a *shade-x-fog* palookup set. Pals to +31 will be taken. , , : intensities of the fog color, [0 .. 63] ]] ) engine.registerMenuFunc( "Translate map for shxfog", CreateMenuFunction{ [0] = shadexfog.translate, { "Starting palnum", 100, MAXUSERPALOOKUP-31 }, { "Fog intensity [0-31]", 0, 31 }, { "Change all sectors' visibility to (Esc: don't)", 0, 255, GNF.NEG_ALLOWED }, }, formatHelp [[ <______________________________________________________> Translates the whole map for use with a shade-x-fog palookup set. .pal becomes the + former .shade .shade becomes the [0 .. 31] If is passed and >= 0, set all sector's visibility to that value. *Notes:* - auto-detects when the translation has been applied with the *same* (if a different one is desired, must reload map). - if shades > 31 or < 0 present, there is loss of information ]] ) engine.registerMenuFunc( "Change pal of everything", CreateMenuFunction{ [0] = shadexfog.challpal, { "Pal to change to", 0, MAXUSERPALOOKUP }, }, formatHelp [[ <__________________________> Changes the .pal member of all sector ceilings/floors, walls and sprites to . ]] ) engine.registerMenuFunc( "Create alpha trans. tabs", CreateMenuFunction{ [0] = shadexfog.create_alpha_trans, { "Starting blendnum", 1, 255 }, { "Number of blending tables", 32, 255 }, { "Fullbright result colors OK?", 0, 1, GNF_BOOL }, }, formatHelp [=[ <____________________________________________________________________> Creates blending tables of smooth alpha translucency, starting with the blending number , with fractions 1/(2\255), 2/(2\255) ... /(2\255). must be a power of two in [1 .. 128]. : should fullbright color indices (>= 240) be permitted as the blending result of two color indices? ]=] ) engine.registerMenuFunc( "Create addtv. trans. tabs", CreateMenuFunction{ [0] = shadexfog.create_additive_trans, { "Starting blendnum", 1, 255 }, { "Number of blending tables", 32, 255 }, { "Fullbright result colors OK?", 0, 1, GNF_BOOL }, }, formatHelp [=[ <____________________________________________________________________> Creates blending tables of smooth additive translucency, starting with the blending number , with fractions 1/(2\255), 2/(2\255) ... /(2\255). must be a power of two in [1 .. 128]. : should fullbright color indices (>= 240) be permitted as the blending result of two color indices? ]=] ) engine.registerMenuFunc( "Create base shade table", CreateMenuFunction{ [0] = shadexfog.create0, { "Pal number", 100, MAXUSERPALOOKUP }, { "Second attempt?", 1, 1, GNF_BOOL }, }, formatHelp [[ <_________________________________> Creates our version of the base shade table at set it to palookup number . : use second attempt instead of the first? This one is more similar to the base shade table shipped with Duke3D, but still shows significant differences. ]] ) engine.registerMenuFunc( "Create depth shade tab", CreateMenuFunction{ [0] = shadexfog.create_depth_shtab, { "Pal number", 100, MAXUSERPALOOKUP }, }, formatHelp [[ <____________________________________> Creates a shade table for debugging purposes at pal . For every color index, the shade table maps shade index to color index (of the first ramp of gray colors, assuming Build has loaded a base shade table with 32 shade levels). ]] ) engine.registerMenuFunc( "Create vismarker sh. tab", CreateMenuFunction{ [0] = shadexfog.create_vismarker_shtab, { "Pal number", 100, MAXUSERPALOOKUP }, }, formatHelp [[ <________________________________________> Creates a shade table for debugging purposes at pal . For every color index, the shade table maps shade index 1 to a ^14bright yellow^O color and shade index 30 to a ^13purple^O color. Thus, it can be useful in visualizing the limits of the fog/visibility attenuation. ]] ) engine.registerMenuFunc( "Create c.index remapping", function() local palnum = getnumber16("Pal number: ", 100, MAXUSERPALOOKUP) if (palnum < 0) then return end local remaptab = {} while (true) do local srchex = getnumber16("Source hexadecatuple (0: finish): ", 0, 14) if (srchex < 0) then return end local dsthex = getnumber16("Destn. hexadecatuple (0: finish): ", 0, 14) if (dsthex < 0) then return end if (srchex == 0 and dsthex == 0) then break end remaptab[srchex] = dsthex end shadexfog.createremap(palnum, remaptab) end, formatHelp [[ <_______________________________________> Creates a color index remapping expressed as mappings of sexdecatuples (16-tuples) of the base palette at pal . Duke3D's default base palette can be considered to consist of six ramps of 32 colors each, three ramps of 16 colors each and a remaining fullbright color set. The sexdecatuples are as follows: < 0, 1>: gray ramp < 2, 3>: skin color ramp < 5, 4>: blue ramp (note that the 16-tuples are in reverse order) < 6, 7>: nightvision yellow/green < 8, 13>: red ramp < 10, 11>: almost gray ramp, but with a slight red hue < 9>: yellow (slightly more red than green) < 12>: "dirty" orange < 14>: blue-purple-red ]] ) engine.registerMenuFunc( "Save pal+sh+trans DAT f.", function() local filename = engine.getstring("File name: ") if (filename == nil) then return end local palnum = getnumber16("Pal number of base shade table: ", 0, MAXUSERPALOOKUP) if (palnum < 0) then return end local blendnum = getnumber16("Blendnum of base transluc. table: ", 0, 255) if (blendnum < 0) then return end local str = engine.getstring("Additional blend numbers (e.g. '64,100-131,255'): ") if (str == nil or str=="") then return end if (not str:find("^[%d,%-]+$")) then error("Additional blending numbers string must contain only digits or ',' or '-'", 2) end local moreblends = {} local didnumstr = {} for n1, n2 in str:gmatch("(%d+)%-(%d+)") do -- parse number ranges moreblends[#moreblends+1] = { tonumber(n1), tonumber(n2) } didnumstr[n1] = true didnumstr[n2] = true end for n in str:gmatch("%d+") do -- parse single numbers if (not didnumstr[n]) then moreblends[#moreblends+1] = tonumber(n) end end local lognumalphatabs if (#moreblends > 0) then lognumalphatabs = getnumber16("log2 of last alpha blending table index (1-7, 0: none): ", 0, 7) if (lognumalphatabs < 0) then return end if (lognumalphatabs == 0) then lognumalphatabs = nil end end shadexfog.save(filename, palnum, blendnum, moreblends, lognumalphatabs) end, formatHelp [[ <___________________________________________________________________> Writes out a full PALETTE.DAT-formatted file named with the base shade table numbered and the base translucency table numbered . Finally, you are asked to specify additional blending tables that can be stored in EDuke32's extended PALETTE.DAT format. If one or more additional blending table is specified, you are also queried for the log2 of the last alpha blending table index, . Since alpha blending tables are assumed to be set up at indices 1 to 2^, it is also the log2 of their total count. ]] ) engine.registerMenuFunc( "Save lookups DAT file", function() local filename = engine.getstring("File name: ") if (filename ~= nil and filename ~= "") then shadexfog.saveLookupDat(filename) end end, formatHelp [[ <_________________________________> Saves the color index lookups (i.e. first 256 values of each shade table) of the pal numbers which have lookups in Duke3D's unaltered LOOKUP.DAT. : the name of the LOOKUP.DAT-formatted file to create ]] ) engine.registerMenuFunc("_________DEBUG_________", function() end --[[ , " \01\02\03\04\05\06\07\08\09\10\11\12\13\14\15\ \16\17\18\19\20\21\22\23\24\25\26\27\28\29\30\31\ \128\129\130\131\132\133\134\135\136\137\138\139\140\141\142\143\ \144\145\146\147\148\149\150\151\152\153\154\155\156\157\158\159\ \160\161\162\163\164\165\166\167\168\169\170\171\172\173\174\175\ \176\177\178\179\180\181\182\183\184\185\186\187\188\189\190\191\ \192\193\194\195\196\197\198\199\200\201\202\203\204\205\206\207\ \208\209\210\211\212\213\214\215\216\217\218\219\220\221\222\223\ \224\225\226\227\228\229\230\231\232\233\234\235\236\237\238\239\ \240\241\242\243\244\245\246\247\248\249\250\251\252\253\254\255" --]] ) engine.registerMenuFunc("Setup dbg. water basepal", engine.setupDebugBasePal, formatHelp [[ <__________________________> Overwrites the water base palette with one where each 16-tuple (except the fullbrights) consists of a single representative color. This can be used to get a quick glimpse about what ramps are present in particular tiles. With this information, custom lookups can be created more directedly with the menu entry. ]] ) engine.registerMenuFunc("Linearize default basep.", engine.linearizeBasePal, formatHelp [[ <_________________________> Overwrites the default base palette with one where certain ramps have their attenuation linearized. This is mainly useful for debugging purposes as it excludes the effect of this nonlinearity for comparison of fog/visibility between classic and OpenGL modes. ]] ) do return shadexfog end