summaryrefslogtreecommitdiff
path: root/graphics/pstricks/contrib/luapstricks/luapstricks-plugin-pstmarble.lua
diff options
context:
space:
mode:
Diffstat (limited to 'graphics/pstricks/contrib/luapstricks/luapstricks-plugin-pstmarble.lua')
-rw-r--r--graphics/pstricks/contrib/luapstricks/luapstricks-plugin-pstmarble.lua662
1 files changed, 662 insertions, 0 deletions
diff --git a/graphics/pstricks/contrib/luapstricks/luapstricks-plugin-pstmarble.lua b/graphics/pstricks/contrib/luapstricks/luapstricks-plugin-pstmarble.lua
new file mode 100644
index 0000000000..d1213a4586
--- /dev/null
+++ b/graphics/pstricks/contrib/luapstricks/luapstricks-plugin-pstmarble.lua
@@ -0,0 +1,662 @@
+---- luapstricks-plugin-pstmarble.lua
+-- Copyright 2021--2023 Marcel Krüger <tex@2krueger.de>
+-- Converted from PostScript in pst-marble.pro version 1.6
+-- pst-marble.pro: Copyright 2018--2019 Aubrey Jaffer
+--
+-- This work may be distributed and/or modified under the
+-- conditions of the LaTeX Project Public License, either version 1.3
+-- of this license or (at your option) any later version.
+-- The latest version of this license is in
+-- http://www.latex-project.org/lppl.txt
+-- and version 1.3 or later is part of all distributions of LaTeX
+-- version 2005/12/01 or later.
+--
+-- This work has the LPPL maintenance status `maintained'.
+--
+-- The Current Maintainer of this work is M. Krüger
+--
+-- This work consists of the files luapstricks.lua and luapstricks-plugin-pstmarble.lua
+
+local loader, version, plugininterface = ...
+assert(loader == 'luapstricks' and version == 0)
+
+local push = plugininterface.push
+local pop = plugininterface.pop
+local pop_array = plugininterface.pop_array
+local pop_num = plugininterface.pop_num
+local pop_proc = plugininterface.pop_proc
+local exec = plugininterface.exec
+
+local newtable = lua.newtable
+local abs = math.abs
+local exp = math.exp
+local cos = math.cos
+local sin = math.sin
+local rad = math.rad
+local max = math.max
+local deg = math.deg
+local atan = math.atan
+
+local function spread(px, py, cx, cy, rad2)
+ local pc2 = (px - cx)^2 + (py - cy)^2 -- (p-c)^2
+ local a = (rad2 / pc2 + 1)^0.5
+ return (px - cx) * a + cx, (py - cy) * a + cy
+end
+
+local e_inv = math.exp(-1)
+local function rake_deformation(px, py, dx, dy, rs, V, tU, Linv)
+ local a = 0
+ for i = 1, #rs do
+ local r = rs[i]
+ local bx, by = dy * r, -dx * r
+ a = a + exp(-abs((px - bx) * dy - (py - by) * dx) * Linv) * tU
+ end
+ return px + dx * a, py + dy * a
+end
+
+local function stir_deformation(px, py, cx, cy, rs, th, Linv, oversample)
+ local dx, dy = (px - cx), (py - cy)
+ local dist = (dx^2 + dy^2)^.5
+ if dist <= 1e-6 then return px, py end
+
+ local a = 0
+ for i = 1, #rs do
+ local r = rs[i]
+ local positive = r > 0
+ if not positive then r = -r end
+ local delta = exp(-abs(dist - r) * (Linv / r)) * th
+ if positive then
+ a = a - delta
+ else
+ a = a + delta
+ end
+ end
+ if oversample > 0 then
+ a = -a
+ end
+ a = rad(a)
+ local cos_a, sin_a = cos(a), sin(a)
+ return cos_a * dx - sin_a * dy + cx, sin_a * dx + cos_a * dy + cy
+end
+
+local function symmod(x, m)
+ local x = x % m
+ if 2 * x >= m then
+ x = x - m
+ end
+ return x
+end
+
+-- Common code to compute inverse of non-linear deformation
+local function g1(mdls, a, mf, af, major, pw, freq)
+ local tmp = mdls / 2
+ if a < 0 then tmp = -tmp end
+
+ local tmp2
+ if mf > 0 then
+ tmp2 = 1 - max(1 - abs(af / 180), 0)^pw
+ else
+ tmp2 = abs(af / 180)^pw
+ end
+ local g0 = tmp * tmp2
+--[[
+ %% one iteration of Newton-Raphson
+ %% g_1=g_0-(g_0-a+(m/2)*sin(g_0*f))/(1+pi*m*f/360*cos(g_0*f))
+]]
+ local gf = rad(g0 * freq)
+ return g0 - (g0 - a + major * sin(gf)) / (1 + mf/2 * cos(gf))
+end
+
+local function jiggle_deformation(px, py, dx, dy, freq, ofst, trv, major, minor, mf, mdls, pw)
+ local a = symmod(py * dx + px * dy + ofst, mdls)
+ local af = a * freq
+ local x, y
+ if mf ~= 0 then
+ --[[
+ % find the minor axis displacement from the major axis value
+ % where it was moved from.
+ ]]
+ local g = g1(mdls, a, mf, af, major, pw, freq)
+ x, y = g - a, cos(rad(g * freq)) * minor
+ else
+ local ang = rad(af)
+ x, y = sin(ang), -cos(ang)
+ -- x, y = x * major, y * minor
+ end
+ return trv[1] * x + trv[3] * y + px, trv[2] * x + trv[4] * y + py
+end
+
+local function wriggle_deformation(px, py, cx, cy, freq, major, minor, mf, mdls, pw)
+ local rd = ((px - cx)^2 + (py - cy)^2)^.5
+ if rd <= 1e-6 then return px, py end
+
+ local a = symmod(rd, mdls)
+ local af = a * freq
+
+ -- x, y are radial and angular displacements from cx,cy
+ -- The naming is used to demonstrate the similarity with jiggle.
+ local x, y
+ if mf ~= 0 then
+ local g = g1(mdls, a, mf, af, major, pw, freq)
+ x, y = g - a, cos(rad(g * freq)) * minor
+ else
+ local ang = rad(af)
+ x, y = sin(ang) * major, -cos(ang) * minor
+ end
+ rd = rd + x
+ local ang = rad(y) + atan(px - cx, py - cy)
+
+ return sin(ang) * rd + cx, cos(ang) * rd + cy
+end
+
+local function stylus_deformation(px, py, bx, by, ex, ey, L, tU, steps, nx, ny, step_x, step_y)
+ for _ = 1, steps do
+ local dxB, dyB = bx - px, by - py
+ local dxE, dyE = ex - px, ey - py
+ local r = (dxB^2 + dyB^2)^.5
+ local denr = r / L
+ if 0 < denr and denr < 6 then
+ local s = (dxE^2 + dyE^2)^.5
+ local txB = dxB * nx + dyB * ny
+ local txE = dxE * nx + dyE * ny
+ local ty = dxB * ny - dyB * nx
+ denr = 2*L*r * exp(denr)
+ local dens = 2*L*s * exp(s / L)
+ local ty2 = ty^2
+ local inx = (L*r - ty2) * tU / denr
+ + (L*s - ty2) * tU / dens
+ local iny = txB * ty * tU / denr
+ + txE * ty * tU / dens
+ px = px + inx * nx + iny * ny
+ py = py + inx * ny - iny * nx
+ end
+ bx, by = ex, ey
+ ex, ey = ex + step_x, ey + step_y
+ end
+ return px, py
+end
+
+-- An irrotational vortex. circ is circulation; t is time in seconds
+local m4o3 = -4/3
+local function vortex_deformation(px, py, cx, cy, circ, t, nuterm)
+ local pc2 = (px - cx)^2 + (py - cy)^2
+ if pc2 < 1e-6 then return px, py end
+ local a = rad((nuterm + (pc2 * t)^.75)^m4o3 * circ)
+ px, py = px - cx, py - cy
+ local cos_a, sin_a = cos(a), sin(a)
+ return cos_a * px - sin_a * py + cx, sin_a * px + cos_a * py + cy
+end
+
+-- We don't actually gain much from moving this one to Lua, but it's more consistent
+local function offset_deformation(px, py, dx, dy)
+ return px + dx, py + dy
+end
+
+local function do_turn(px, py, cx, cy, trv)
+ px, py = px - cx, py - cy
+ return trv[1] * px + trv[3] * py + trv[5], trv[2] * px + trv[4] * py + trv[6]
+end
+
+local function ct_handler(handler)
+ return function(px, py, args, count)
+ push(px)
+ push(py)
+
+ for j = 1, count do
+ push(args[j])
+ end
+ exec(handler)
+ py = pop_num()
+ px = pop_num()
+ return px, py
+ end
+end
+
+local function ct_dispatch_fallback(fallback)
+ local handler = ct_handler(fallback)
+ return function(ct, px, py, args, count)
+ push{kind = 'name', value = 'ct'}
+ push{kind = 'name', value = ct}
+ exec'def'
+
+ return handler(px, py, args, count)
+ end
+end
+
+local ct_handlers = {
+ offset = function(px, py, args, count)
+ assert(count == 2)
+ return offset_deformation(px, py, args[1], args[2])
+ end,
+ -- offset = ct_handler'offset-deformation',
+ turn = function(px, py, args, count)
+ assert(count == 3)
+ return do_turn(px, py, args[1], args[2], args[3].value)
+ end,
+ -- turn = ct_handler'do-turn',
+ jiggle = function(px, py, args, count)
+ assert(count == 10)
+ return jiggle_deformation(px, py, args[1], args[2], args[3], args[4], args[5].value, args[6], args[7], args[8], args[9], args[10])
+ end,
+ -- jiggle = ct_handler'jiggle-deformation',
+ rake = function(px, py, args, count)
+ assert(count == 6)
+ return rake_deformation(px, py, args[1], args[2], args[3].value, args[4], args[5], args[6])
+ end,
+ -- rake = ct_handler'rake-deformation',
+ vortex = function(px, py, args, count)
+ assert(count == 4)
+ exec'nuterm'
+ local nuterm = pop_num()
+ return vortex_deformation(px, py, args[1], args[2], args[3], args[4], nuterm)
+ end,
+ -- vortex = ct_handler'vortex-deformation',
+ stir = function(px, py, args, count)
+ assert(count == 5)
+ exec'oversample'
+ local oversample = pop_num()
+ return stir_deformation(px, py, args[1], args[2], args[3].value, args[4], args[5], oversample)
+ end,
+ -- stir = ct_handler'stir-deformation',
+ wriggle = function(px, py, args, count)
+ assert(count == 8)
+ return wriggle_deformation(px, py, args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8])
+ end,
+ -- wriggle = ct_handler'wriggle-deformation',
+ stylus = function(px, py, args, count)
+ assert(count == 11)
+ return stylus_deformation(px, py, args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11])
+ end,
+ -- stylus = ct_handler'stylus-deformation',
+}
+local function ct_dispatch(fallback)
+ fallback = ct_dispatch_fallback(fallback)
+ return function(ct, px, py, args, count)
+ local handler = ct_handlers[ct]
+ if handler then
+ return handler(px, py, args, count)
+ else
+ return fallback(ct, px, py, args, count)
+ end
+ end
+end
+
+local function sharpen(x)
+ x = x - 0.5
+ if abs(x) >= 1e-8 then
+ x = x / abs(x)^.66 * .63
+ end
+ return x + 0.5
+end
+
+local function Vmap2(v1, v2, func)
+ local result = newtable(#v2, 0)
+ for i=1, #v1 do
+ result[i] = func(v1[i], v2[i])
+ end
+ return result
+end
+
+local function paper_shading(rgb, pwr, paper)
+ return Vmap2(rgb, paper, function(c1, c2)
+ if c2 >= c1 then
+ local a = 1 - c1/c2
+ if a >= 1e-30 then
+ a = a^pwr
+ end
+ return (1 - a) * c2
+ else
+ local a = (1 - c1) / (1 - c2)
+ if a >= 1e-30 then
+ a = a^(2-pwr)
+ end
+ return 1 - a * (1 - c2)
+ end
+ end)
+end
+
+local function actions2rgb(fallback)
+ local dispatch = ct_dispatch(fallback)
+ return function(px, py, actions, acnt, paper)
+ local cdx = acnt
+ for cdx = acnt, 1, -1 do
+ local action = actions[cdx]
+ local kind = action.kind
+ if kind == 'executable' then
+ action = action.value
+ kind = action.kind
+ end
+ assert(kind == 'array')
+ action = action.value
+ local count = #action
+ local ct = action[count].value
+ if ct == 'drop' then
+ assert(count == 8)
+ local cx, cy = action[1], action[2]
+ local rad2 = action[3]
+ local bgc, rgb = action[4].value, action[5].value
+ local sr2, gc = action[6], action[7]
+
+ local a2 = (px - cx)^2 + (py - cy)^2
+ local disc = a2 < 1e-10 and 0 or 1 - rad2 / a2
+ if disc <= 0 then
+ if gc ~= 0 then
+ rgb = paper_shading(rgb, exp(a2 * sr2) * gc, paper)
+ end
+ if disc > -0.001 then
+ local a = sharpen((-disc)^.5)
+ rgb = Vmap2(rgb, bgc, function(v1, v2) return v1 * a + v2 * (1-a) end)
+ end
+ return rgb
+ else
+ local a = disc^.5
+ px, py = (px - cx) * a + cx, (py - cy) * a + cy
+ end
+ else
+ px, py = dispatch(ct, px, py, action, count - 1)
+ end
+ end
+ end
+end
+
+-- At this point, fx and fy contain the raster coordinates;
+-- [ r g b ] is on top of the stack.
+local function do_shadings(rgb, fx, fy, shadings, paper)
+ local scnt = #shadings
+ if scnt > 0 then
+ for idx = 1, scnt do
+ local shading = shadings[idx]
+ local kind = shading.kind
+ if kind == 'executable' then
+ shading = shading.value
+ kind = shading.kind
+ end
+ assert(kind == 'array')
+ shading = shading.value
+ local count = #shading
+ local ct = shading[count].value
+ if ct == 'jiggle-shade' then
+ assert(count == 9)
+ local dx, dy = shading[1], shading[2]
+ local freq, ofst = shading[3], shading[4]
+ local major, mf = shading[5], shading[6]
+ local mdls, pw = shading[7], shading[8]
+
+ local a = symmod(fy * dx + fx * dy + ofst, mdls)
+ local af = a * freq
+
+ if mf ~= 0 then
+ local g = g1(mdls, a, mf, af, major, pw, freq)
+ rgb = paper_shading(rgb, max(cos(rad(g * freq)) * mf + 1, 0), paper)
+ end
+ elseif ct == 'wriggle-shade' then
+ assert(count == 9)
+ local cx, cy = shading[1], shading[2]
+ local freq, ofst = shading[3], shading[4]
+ local major, mf = shading[5], shading[6]
+ local mdls, pw = shading[7], shading[8]
+
+ local a = symmod(((fx - cx)^2 + (fy - cy)^2)^.5 + ofst, mdls)
+ local af = a * freq
+
+ if mf ~= 0 then
+ local g = g1(mdls, a, mf, af, major, pw, freq)
+ rgb = paper_shading(rgb, max(cos(rad(g * freq)) * mf + 1, 0), paper)
+ end
+ else
+ print(string.format('unrecognized shade token %s', ct))
+ end
+ end
+ end
+
+ return rgb
+end
+
+local function do_raster(dispatch)
+ local actions2rgb = actions2rgb(dispatch)
+ return function(lox, hix, loy, hiy, oversample, actions, acnt, shadings, paper, scl, background)
+ local sampleover = 1 / oversample
+ local width = (hix - lox) // sampleover
+ local height = (hiy - loy) // sampleover
+ local raster = newtable(width * height, 0)
+
+ local factor = sampleover / scl
+
+ for y = 0, height do
+ local fy = loy / scl + y * factor
+ for x = 0, width do
+ local fx = lox / scl + x * factor
+
+ local rgb = actions2rgb(fx, fy, actions, acnt, paper) or background
+ rgb = do_shadings(rgb, fx, fy, shadings, paper)
+ raster[1 + y*(width+1) + x] = {kind = 'array', value = rgb}
+ end
+ end
+ return raster, width + 1, height + 1
+ end
+end
+
+return {
+ spread = function()
+ local rad2 = pop_num() -- rad^2
+ local cy = pop_num()
+ local cx = pop_num()
+ local py = pop_num()
+ local px = pop_num()
+ px, py = spread(px, py, cx, cy, rad2)
+ push(px)
+ push(py)
+ end,
+ ['.actions2rgb'] = function() -- px py actions acnt paper fallback .composite-map exec
+ local _, fallback = pop_proc()
+ local actions2rgb = actions2rgb(fallback)
+ push(function()
+ local paper = pop_array().value
+ local acnt = pop_num()
+ local actions = pop_array().value
+
+ local py = pop_num()
+ local px = pop_num()
+
+ local rgb = actions2rgb(px, py, actions, acnt, paper)
+ if rgb then
+ push{kind = 'array', value = rgb}
+ push(true)
+ else
+ push(false)
+ end
+ end)
+ end,
+ ['.paper-shading'] = function() -- rgb pwr paper
+ local paper = pop_array().value
+ local pwr = pop_num()
+ local rgb = pop_array().value
+ rgb = paper_shading(rgb, pwr, paper)
+ push{kind = 'array', value = rgb}
+ end,
+ ['.composite-map'] = function() -- acnt idx actions fallback .composite-map exec
+ local _, fallback = pop_proc()
+ local dispatch = ct_dispatch(fallback)
+ push(function()
+ local actions = pop_array().value
+ local idx = pop_num()
+ local acnt = pop_num()
+
+ local py = pop_num()
+ local px = pop_num()
+
+ for i = idx + 1, acnt - 1 do
+ local action = actions[i+1]
+ local kind = action.kind
+ if kind == 'executable' then
+ action = action.value
+ kind = action.kind
+ end
+ assert(kind == 'array')
+ action = action.value
+ local count = #action
+ local ct = action[count].value
+ if ct == 'drop' then
+ assert(count == 8)
+ px, py = spread(px, py, action[1], action[2], action[3])
+ else
+ px, py = dispatch(ct, px, py, action, count - 1)
+ end
+ end
+ push(px)
+ push(py)
+ end)
+ end,
+ ['stir-deformation'] = function()
+ local Linv = pop_num()
+ local th = pop_num()
+ local rs = pop_array().value
+ local cy = pop_num()
+ local cx = pop_num()
+ local py = pop_num()
+ local px = pop_num()
+
+ exec'oversample'
+ local oversample = pop_num()
+ px, py = stir_deformation(px, py, cx, cy, rs, th, Linv, oversample)
+ push(px)
+ push(py)
+ end,
+ ['rake-deformation'] = function()
+ local Linv = pop_num()
+ local tU = pop_num()
+ local V = pop_num()
+ local rs = pop_array().value
+ local dy = pop_num()
+ local dx = pop_num()
+ local py = pop_num()
+ local px = pop_num()
+ px, py = rake_deformation(px, py, dx, dy, rs, V, tU, Linv)
+ push(px)
+ push(py)
+ end,
+ ['jiggle-deformation'] = function()
+ local pw = pop_num()
+ local mdls = pop_num()
+ local mf = pop_num()
+ local minor = pop_num()
+ local major = pop_num()
+ local trv = pop_array().value
+ local ofst = pop_num()
+ local freq = pop_num()
+ local dy = pop_num()
+ local dx = pop_num()
+ local py = pop_num()
+ local px = pop_num()
+ px, py = jiggle_deformation(px, py, dx, dy, freq, ofst, trv, major, minor, mf, mdls, pw)
+ push(px)
+ push(py)
+ end,
+ ['wriggle-deformation'] = function()
+ local pw = pop_num()
+ local mdls = pop_num()
+ local mf = pop_num()
+ local minor = pop_num()
+ local major = pop_num()
+ local freq = pop_num()
+ local cy = pop_num()
+ local cx = pop_num()
+ local py = pop_num()
+ local px = pop_num()
+ px, py = wriggle_deformation(px, py, cx, cy, freq, major, minor, mf, mdls, pw)
+ push(px)
+ push(py)
+ end,
+ ['stylus-deformation'] = function()
+ local step_y = pop_num()
+ local step_x = pop_num()
+ local ny = pop_num()
+ local nx = pop_num()
+ local steps = pop_num()
+ local tU = pop_num()
+ local L = pop_num()
+ local ey = pop_num()
+ local ex = pop_num()
+ local by = pop_num()
+ local bx = pop_num()
+ local py = pop_num()
+ local px = pop_num()
+ px, py = stylus_deformation(px, py, bx, by, ex, ey, L, tU, steps, nx, ny, step_x, step_y)
+ push(px)
+ push(py)
+ end,
+ ['.vortex-deformation'] = function()
+ local nuterm = pop_num()
+ local t = pop_num()
+ local circ = pop_num()
+ local cy = pop_num()
+ local cx = pop_num()
+ local py = pop_num()
+ local px = pop_num()
+
+ px, py = vortex_deformation(px, py, cx, cy, circ, t, nuterm)
+ push(px)
+ push(py)
+ end,
+ ['offset-deformation'] = function()
+ local dy = pop_num()
+ local dx = pop_num()
+ local py = pop_num()
+ local px = pop_num()
+
+ px, py = offset_deformation(px, py, dx, dy)
+ push(px)
+ push(py)
+ end,
+ ['do-turn'] = function()
+ local trv = pop_array().value
+ local cy = pop_num()
+ local cx = pop_num()
+ local py = pop_num()
+ local px = pop_num()
+
+ px, py = do_turn(px, py, cx, cy, trv)
+ push(px)
+ push(py)
+ end,
+ ['.Minsky-circle'] = function()
+ local eps = pop_num()
+ local y = pop_num()
+ local x = pop_num()
+ x = x - eps * y
+ y = y + eps * x
+ push(x)
+ push(y)
+ end,
+ ['.do-shadings'] = function()
+ local paper = pop_array().value
+ local shadings = pop_array().value
+ local fy = pop_num()
+ local fx = pop_num()
+ local rgb = pop_array().value
+ rgb = do_shadings(rgb, fx, fy, shadings, paper)
+ push{kind = 'array', value = rgb}
+ end,
+ ['.do-raster'] = function()
+ local _, fallback = pop_proc()
+ local do_raster = do_raster(fallback)
+ push(function()
+ local background = pop_array().value
+ local scl = pop_num()
+ local paper = pop_array().value
+ local shadings = pop_array().value
+ local acnt = pop_num()
+ local actions = pop_array().value
+ local oversample = pop_num()
+ local hiy = pop_num()
+ local loy = pop_num()
+ local hix = pop_num()
+ local lox = pop_num()
+
+ local raster, width, height = do_raster(lox, hix, loy, hiy, oversample, actions, acnt, shadings, paper, scl, background)
+ push{kind = 'array', value = raster}
+ push(width)
+ push(height)
+ end)
+ end,
+}, 0