summaryrefslogtreecommitdiff
path: root/tex/context/base/mkiv/mlib-ran.lmt
diff options
context:
space:
mode:
Diffstat (limited to 'tex/context/base/mkiv/mlib-ran.lmt')
-rw-r--r--tex/context/base/mkiv/mlib-ran.lmt237
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