diff options
author | Hans Hagen <pragma@wxs.nl> | 2020-07-14 00:25:53 +0200 |
---|---|---|
committer | Context Git Mirror Bot <phg@phi-gamma.net> | 2020-07-14 00:25:53 +0200 |
commit | f1129626606384a7a55a21a83531f51f8b5dee25 (patch) | |
tree | 9bfaf9a8bf2c218007291023f771babc9ac2e1d9 /tex/context/base/mkiv/mlib-ran.lmt | |
parent | b821116421f0d942052ad225f4ea62aef2696817 (diff) | |
download | context-f1129626606384a7a55a21a83531f51f8b5dee25.tar.gz |
2020-07-13 23:52:00
Diffstat (limited to 'tex/context/base/mkiv/mlib-ran.lmt')
-rw-r--r-- | tex/context/base/mkiv/mlib-ran.lmt | 237 |
1 files changed, 237 insertions, 0 deletions
diff --git a/tex/context/base/mkiv/mlib-ran.lmt b/tex/context/base/mkiv/mlib-ran.lmt new file mode 100644 index 000000000..cb8645e8d --- /dev/null +++ b/tex/context/base/mkiv/mlib-ran.lmt @@ -0,0 +1,237 @@ +if not modules then modules = { } end modules ['mlib-ran'] = { + version = 1.001, + comment = "companion to mlib-ctx.mkiv", + author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", + copyright = "PRAGMA ADE / ConTeXt Development Team", + license = "see context related readme files", +} + +local next = next +local ceil, floor, random, sqrt, cos, sin, pi, max, min = math.ceil, math.floor, math.random, math.sqrt, math.cos, math.sin, math.pi, math.min, math.max +local remove = table.remove + +-- Below is a bit of rainy saturday afternoon hobyism, while listening to Judith +-- Owens redisCOVERed (came there via Leland Sklar who I have on a few live blurays; +-- and who is also on YT). (Also nice: https://www.youtube.com/watch?v=GXqasIRaxlA) + +-- When Aditya pointed me to an article on mazes I ended up at poison distributions +-- which to me looks nicer than what I normally do, fill a grid and then randomize +-- the resulting positions. With some hooks this can be used for interesting patterns +-- too. A few links: +-- +-- https://bost.ocks.org/mike/algorithms/#maze-generation +-- https://extremelearning.com.au/ +-- https://www.jasondavies.com/maps/random-points/ +-- http://devmag.org.za/2009/05/03/poisson-disk-sampling + +-- The next function is quite close to what us discribed in the poisson-disk-sampling +-- link mentioned before. One can either use a one dimensional grid array or a two +-- dimensional one. The example code uses some classes dealing with points. In the +-- process I added some more control. + +-- we could do without the samplepoints list + +local function poisson(width, height, mindist, newpointscount, initialx, initialy) + local starttime = os.clock() + local cellsize = mindist / sqrt(2) + local nofwidth = ceil(width // cellsize) + local nofheight = ceil(height // cellsize) + local grid = lua.newtable(nofwidth,0) -- table.setmetatableindex("table") + local firstx = initialx or random() * width + local firsty = initialy or random() * height + local firstpoint = { firstx, firsty, 1 } + -- local samplepoints = { firstpoint } + local processlist = { firstpoint } + local nofprocesslist = 1 + local nofsamplepoints = 1 + local twopi = 2 * pi + + for i=1,nofwidth do + local g = lua.newindex(nofheight,false) + grid[i] = g + end + + local x = floor(firstx // cellsize) + 1 -- lua indices + local y = floor(firsty // cellsize) + 1 -- lua indices + + x = max(1, min(x, width - 1)) + y = max(1, min(y, height - 1)) + + grid[x][y] = firstpoint + + -- The website shows graphic for this 5*5 grid snippet, if we use a one dimentional + -- array then we could have one loop; a first version used a metatable trick so we + -- had grid[i+gx][j+gy] but we no we also return the grid, so ... we now just check. + + -- There is no need for the samplepoints list as we can get that from the grid but + -- instead we can store the index with the grid. + + while nofprocesslist > 0 do + local point = remove(processlist,random(1,nofprocesslist)) + nofprocesslist = nofprocesslist - 1 + for i=1,newpointscount do -- we start at 1 + local radius = mindist * (random() + 1) + local angle = twopi * random() + local nx = point[1] + radius * cos(angle) + local ny = point[2] + radius * sin(angle) + if nx > 1 and ny > 1 and nx <= width and ny <= height then -- lua indices + local gx = floor(nx // cellsize) + local gy = floor(ny // cellsize) + -- the 5x5 cells around the point + for i=-2,2 do + for j=-2,2 do + local cell = grid[i + gx] + if cell then + cell = cell[j + gy] + if cell and sqrt((cell[1] - nx)^2 + (cell[2] - ny)^2) < mindist then + goto next + end + end + end + end + -- local newpoint = { nx, ny } + nofprocesslist = nofprocesslist + 1 + nofsamplepoints = nofsamplepoints + 1 + local newpoint = { nx, ny, nofsamplepoints } + processlist [nofprocesslist] = newpoint + -- samplepoints[nofsamplepoints] = newpoint + grid[gx][gy] = newpoint + end + ::next:: + end + end + + return { + count = nofsamplepoints, + -- points = samplepoints, + grid = grid, + time = os.clock() - starttime, + } +end + +-- For now: + +local randomizers = utilities.randomizers or { } +utilities.randomizers = randomizers +randomizers.poisson = poisson + +-- The MetaFun interface: + +local formatters = string.formatters +local concat = table.concat + +local f_macro = formatters["%s(%N,%N);"] + +local f_macros = { + [2] = formatters["%s(%N,%N);"], + [3] = formatters["%s(%N,%N,%i);"], + [4] = formatters["%s(%N,%N,%i,%i);"], +} + +function grid_to_mp(t,f,n) + local grid = t.grid + local count = t.count + local result = { } + local r = 0 + local macro = f or "draw" + local runner = f_macros[n or 2] or f_macros[2] + for i=1,#grid do + local g = grid[i] + if g then + for j=1,#g do + local v = g[j] + if v then + r = r + 1 + result[r] = runner(macro,v[1],v[2],v[3],count) + end + end + end + end + return concat(result, " ") +end + +local getparameter = metapost.getparameter + +local function lmt_poisson() + local initialx = getparameter { "initialx" } + local initialy = getparameter { "initialy" } + local width = getparameter { "width" } + local height = getparameter { "height" } + local distance = getparameter { "distance" } + local count = getparameter { "count" } + + local result = poisson ( + width, height, distance, count, + initialx > 0 and initialx or false, + initialy > 0 and initialy or false + ) + + if result then + logs.report("poisson","w=%N, h=%N, d=%N, c=%N, n=%i, runtime %.3f", + width, height, distance, count, result.count, result.time + ) + end + + return result +end + +function mp.lmt_poisson_generate() + local result = lmt_poisson() + if result then + return grid_to_mp ( + result, + getparameter { "macro" }, + getparameter { "arguments" } + ) + end +end + +-- -- some playing around showed no benefit +-- +-- function points_to_mp(t,f) +-- local points = t.points +-- local count = t.count +-- local result = { } +-- local r = 0 +-- local macro = f or "draw" +-- local runner = f_macros[n or 2] or f_macros[2] +-- for i=1,count do +-- local v = points[i] +-- r = r + 1 +-- result[r] = runner(macro,v[1],v[2],v[3],count) +-- end +-- return concat(result, " ") +-- end +-- +-- local result = false +-- local i, j, n = 0, 0, 0 +-- +-- function mp.lmt_poison_start() +-- result = lmt_poisson() +-- end +-- +-- function mp.lmt_poisson_stop() +-- result = false +-- end +-- +-- function mp.lmt_poisson_count() +-- return result and result.count or 0 +-- end +-- +-- function mp.lmt_poisson_get(i) +-- if result then +-- return mp.pair(result.points[i]) +-- end +-- end +-- +-- function mp.lmt_poisson_generate() +-- mp.lmt_poisson_start() +-- if result then +-- return grid_to_mp ( +-- result, +-- getparameter { "macro" }, +-- getparameter { "arguments" } +-- ) +-- end +-- mp.lmt_poisson_stop() +-- end |