|
|
|
@ -4,61 +4,117 @@ local args = {...}
|
|
|
|
|
local rng = dfhack.random.new(3)
|
|
|
|
|
|
|
|
|
|
if #args < 3 then
|
|
|
|
|
qerror('Usage: devel/test-perlin <fname.pgm> <density> <coeff|expr...x[...y[...z]]>...')
|
|
|
|
|
qerror('Usage: devel/test-perlin <fname.pgm> <density> <expr> [-xy <xyscale>] [-z <zscale>]')
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local fname = table.remove(args,1)
|
|
|
|
|
local goal = tonumber(table.remove(args,1)) or qerror('Invalid density')
|
|
|
|
|
local expr = table.remove(args,1) or qerror('No expression')
|
|
|
|
|
local zscale = 2
|
|
|
|
|
local xyscale = 1
|
|
|
|
|
|
|
|
|
|
local zscale = 6
|
|
|
|
|
|
|
|
|
|
local coeffs = {}
|
|
|
|
|
local fns = {}
|
|
|
|
|
for i = 1,#args,2 do
|
|
|
|
|
if args[i] == '-xy' then
|
|
|
|
|
xyscale = tonumber(args[i+1]) or qerror('Invalid xyscale')
|
|
|
|
|
end
|
|
|
|
|
if args[i] == '-z' then
|
|
|
|
|
zscale = tonumber(args[i+1]) or qerror('Invalid zscale')
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local env = copyall(math)
|
|
|
|
|
env.apow = function(x,y) return math.pow(math.abs(x),y) end
|
|
|
|
|
local fn_env = copyall(math)
|
|
|
|
|
|
|
|
|
|
fn_env.rng = rng
|
|
|
|
|
fn_env.apow = function(x,y) return math.pow(math.abs(x),y) end
|
|
|
|
|
fn_env.spow = function(x,y) return x*math.pow(math.abs(x),y-1) end
|
|
|
|
|
|
|
|
|
|
-- Noise functions are referenced from expressions
|
|
|
|
|
-- as variables of form like "x3a", where:
|
|
|
|
|
-- 1) x is one of x/y/z/w independent functions in each octave
|
|
|
|
|
-- 2) 3 is the octave number; 0 corresponds to the whole range
|
|
|
|
|
-- 3) a is the subtype
|
|
|
|
|
|
|
|
|
|
-- Independent noise functions: support 4
|
|
|
|
|
local ids = { 'x', 'y', 'z', 'w' }
|
|
|
|
|
-- Subtype: provides an offset to the coordinates
|
|
|
|
|
local subs = {
|
|
|
|
|
[''] = { 0, 0, 0 },
|
|
|
|
|
a = { 0.5, 0, 0 },
|
|
|
|
|
b = { 0, 0.5, 0 },
|
|
|
|
|
c = { 0.5, 0.5, 0 },
|
|
|
|
|
d = { 0, 0, 0.5 },
|
|
|
|
|
e = { 0.5, 0, 0.5 },
|
|
|
|
|
f = { 0, 0.5, 0.5 },
|
|
|
|
|
g = { 0.5, 0.5, 0.5 },
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function mkdelta(v)
|
|
|
|
|
if v == 0 then
|
|
|
|
|
return ''
|
|
|
|
|
else
|
|
|
|
|
return '+'..v
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
for i = 1,#args do
|
|
|
|
|
local fn = rng:perlin(3);
|
|
|
|
|
local fn2 = rng:perlin(3);
|
|
|
|
|
local fn3 = rng:perlin(3);
|
|
|
|
|
local fn4 = rng:perlin(3);
|
|
|
|
|
function mkexpr(expr)
|
|
|
|
|
-- Collect referenced variables
|
|
|
|
|
local max_octave = -1
|
|
|
|
|
local vars = {}
|
|
|
|
|
|
|
|
|
|
fns[i] = fn;
|
|
|
|
|
for var,id,octave,subtype in string.gmatch(expr,'%f[%w](([xyzw])(%d+)(%a*))%f[^%w]') do
|
|
|
|
|
if not vars[var] then
|
|
|
|
|
octave = tonumber(octave)
|
|
|
|
|
|
|
|
|
|
local val = string.match(args[i],'^([+-]?[%d.]+)$')
|
|
|
|
|
if val then
|
|
|
|
|
coeffs[i] = function(x) return x * val end
|
|
|
|
|
else
|
|
|
|
|
local argstr = 'x'
|
|
|
|
|
if string.match(args[i], '%f[%w]w%f[^%w]') then
|
|
|
|
|
argstr = 'x,y,z,w'
|
|
|
|
|
fns[i] = function(x,y,z)
|
|
|
|
|
return fn(x,y,z), fn2(x,y,z), fn3(x+0.5,y+0.5,z+0.5), fn4(x+0.5,y+0.5,z+0.5)
|
|
|
|
|
end
|
|
|
|
|
elseif string.match(args[i], '%f[%w]z%f[^%w]') then
|
|
|
|
|
argstr = 'x,y,z'
|
|
|
|
|
fns[i] = function(x,y,z)
|
|
|
|
|
return fn(x,y,z), fn2(x,y,z), fn3(x+0.5,y+0.5,z+0.5)
|
|
|
|
|
end
|
|
|
|
|
elseif string.match(args[i], '%f[%w]y%f[^%w]') then
|
|
|
|
|
argstr = 'x,y'
|
|
|
|
|
fns[i] = function(x,y,z)
|
|
|
|
|
return fn(x,y,z), fn2(x,y,z)
|
|
|
|
|
if octave > max_octave then
|
|
|
|
|
max_octave = octave
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local sub = subs[subtype] or qerror('Invalid subtype: '..subtype)
|
|
|
|
|
|
|
|
|
|
vars[var] = { id = id, octave = octave, subtype = subtype, sub = sub }
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
if max_octave < 0 then
|
|
|
|
|
qerror('No noise function references in expression.')
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local f,err = load(
|
|
|
|
|
'return function('..argstr..') return ('..args[i]..') end',
|
|
|
|
|
'=(expr)', 't', env
|
|
|
|
|
)
|
|
|
|
|
if not f then
|
|
|
|
|
qerror(err)
|
|
|
|
|
-- Allocate the noise functions
|
|
|
|
|
local code = ''
|
|
|
|
|
|
|
|
|
|
for i = 0,max_octave do
|
|
|
|
|
for j,id in ipairs(ids) do
|
|
|
|
|
code = code .. 'local _fn_'..i..'_'..id..' = rng:perlin(3)\n';
|
|
|
|
|
end
|
|
|
|
|
coeffs[i] = f()
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- Evaluate variables
|
|
|
|
|
code = code .. 'return function(x,y,z)\n'
|
|
|
|
|
|
|
|
|
|
for var,info in pairs(vars) do
|
|
|
|
|
local fn = '_fn_'..info.octave..'_'..info.id
|
|
|
|
|
local mul = math.pow(2,info.octave)
|
|
|
|
|
mul = math.min(48*4, mul)
|
|
|
|
|
code = code .. ' local '..var
|
|
|
|
|
.. ' = _fn_'..info.octave..'_'..info.id
|
|
|
|
|
.. '(x*'..mul..mkdelta(info.sub[1])
|
|
|
|
|
.. ',y*'..mul..mkdelta(info.sub[2])
|
|
|
|
|
.. ',z*'..mul..mkdelta(info.sub[3])
|
|
|
|
|
.. ')\n'
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- Complete and compile the function
|
|
|
|
|
code = code .. ' return ('..expr..')\nend\n'
|
|
|
|
|
|
|
|
|
|
local f,err = load(code, '=(expr)', 't', fn_env)
|
|
|
|
|
if not f then
|
|
|
|
|
qerror(err)
|
|
|
|
|
end
|
|
|
|
|
return f()
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local field_fn = mkexpr(expr)
|
|
|
|
|
|
|
|
|
|
function render(thresh,file)
|
|
|
|
|
local area = 0
|
|
|
|
|
local line, arr = '', {}
|
|
|
|
@ -68,16 +124,10 @@ function render(thresh,file)
|
|
|
|
|
line = ''
|
|
|
|
|
for zx = 0,1 do
|
|
|
|
|
for x = 0,48*4-1 do
|
|
|
|
|
local tx = (0.5+x)/(48*2)
|
|
|
|
|
local ty = (0.5+y)/(48*2)
|
|
|
|
|
local tz = 0.5+(zx+zy*2)/(48*2/zscale)
|
|
|
|
|
local v1 = 0
|
|
|
|
|
for i = 1,#coeffs do
|
|
|
|
|
v1 = v1 + coeffs[i](fns[i](tx,ty,tz))
|
|
|
|
|
tx = tx*2
|
|
|
|
|
ty = ty*2
|
|
|
|
|
tz = tz*2
|
|
|
|
|
end
|
|
|
|
|
local tx = (0.5+x)/(48*4/xyscale)
|
|
|
|
|
local ty = (0.5+y)/(48*4/xyscale)
|
|
|
|
|
local tz = 0.3+(zx+zy*2)/(48*4/zscale)
|
|
|
|
|
local v1 = field_fn(tx, ty, tz)
|
|
|
|
|
local v = -1
|
|
|
|
|
if v1 > thresh then
|
|
|
|
|
v = v1;
|
|
|
|
@ -103,12 +153,13 @@ function render(thresh,file)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function search(fn,min,max,goal,eps)
|
|
|
|
|
while true do
|
|
|
|
|
local center = (max+min)/2
|
|
|
|
|
local center
|
|
|
|
|
for i = 1,32 do
|
|
|
|
|
center = (max+min)/2
|
|
|
|
|
local cval = fn(center)
|
|
|
|
|
print('At '..center..': '..cval)
|
|
|
|
|
if math.abs(cval-goal) < eps then
|
|
|
|
|
return center
|
|
|
|
|
break
|
|
|
|
|
end
|
|
|
|
|
if cval > goal then
|
|
|
|
|
min = center
|
|
|
|
@ -116,16 +167,19 @@ function search(fn,min,max,goal,eps)
|
|
|
|
|
max = center
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
return center
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local thresh = search(render, -1, 1, goal, 0.001)
|
|
|
|
|
local thresh = search(render, -2, 2, goal, math.min(0.001,goal/20))
|
|
|
|
|
|
|
|
|
|
local file,err = io.open(fname, 'wb')
|
|
|
|
|
if not file then
|
|
|
|
|
print('error: ',err)
|
|
|
|
|
return
|
|
|
|
|
end
|
|
|
|
|
file:write('P5\n768 768\n255\n')
|
|
|
|
|
file:write('P5\n')
|
|
|
|
|
file:write('# '..goal..' '..expr..' '..xyscale..' '..zscale..'\n')
|
|
|
|
|
file:write('768 768\n255\n')
|
|
|
|
|
local area = render(thresh, file)
|
|
|
|
|
file:close()
|
|
|
|
|
|
|
|
|
|