--[[ 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 type = type local unpack = unpack local bit = require("bit") local math = require("math") 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) local ok, errmsg = engine.savePaletteDat(filename, palnum, blendnum, moreblends) if (not ok) then error(errmsg) end printf('Wrote base palette, shade and translucency tables to "%s".', filename) if (moreblends ~= nil) then printf(" Also wrote additional translucency tables.") 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] } 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 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 }, } ) 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 }, } ) engine.registerMenuFunc( "Change pal of everything", CreateMenuFunction{ [0] = shadexfog.challpal, { "Pal to change to", 0, MAXUSERPALOOKUP }, } ) 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 }, } ) 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 }, } ) engine.registerMenuFunc( "Create base shade table", CreateMenuFunction{ [0] = shadexfog.create0, { "Pal number", 100, MAXUSERPALOOKUP }, { "Second attempt?", 1, 1, GNF_BOOL }, } ) engine.registerMenuFunc( "Create depth shade tab", CreateMenuFunction{ [0] = shadexfog.create_depth_shtab, { "Pal number", 100, MAXUSERPALOOKUP }, } ) engine.registerMenuFunc( "Create vismarker sh. tab", CreateMenuFunction{ [0] = shadexfog.create_vismarker_shtab, { "Pal number", 100, MAXUSERPALOOKUP }, } ) engine.registerMenuFunc( "Create c.index remapping", function() local palnum = getnumber16("Pal number: ", 100, MAXUSERPALOOKUP, df) if (palnum < 0) then return end local remaptab = {} while (true) do local srchex = getnumber16("Source hexadecatuple (0: finish): ", 0, 14, df) if (srchex < 0) then return end local dsthex = getnumber16("Destn. hexadecatuple (0: finish): ", 0, 14, df) if (dsthex < 0) then return end if (srchex == 0 and dsthex == 0) then break end remaptab[srchex] = dsthex end shadexfog.createremap(palnum, remaptab) end ) engine.registerMenuFunc("_________DEBUG_________", function() end) engine.registerMenuFunc("Setup dbg. water basepal", engine.setupDebugBasePal) engine.registerMenuFunc("Linearize default basep.", engine.linearizeBasePal) do return shadexfog end