6

https://tex.stackexchange.com/a/749115/319072 shows how to make a Hilbert curve of arbitrary order using lua and tikz.

I want to use lua to make a luametafun command which does the same thing.

This is my non-working attempt (not working in that the pieces don't connect properly):

% jasper.tex
\ctxloadluafile{jasper.lua}
\processMPfigurefile{jasper.mp}
\starttext
  \dorecurse{5}{
    \startMPpage
      path outline ;
      outline := (0,0) -- (2,0) -- (2,2) -- (0,2) -- cycle ;
      draw outline scaled 1cm ;
      jasper_hilbert[
          order = #1
      ] ;
    \stopMPpage
  }
\stoptext
-- jasper.lua
local tau = 2*math.pi 
local cos, sin = math.cos, math.sin

--- matrix multiplication
---
--- @param A table<table<number>> left matrix
--- @param B table<table<number>> right matrix
--- @return table<table<number>> the product
local function matrix_multiply(A, B)
    local rows_A = #A
    local columns_A = #A[1]
    local rows_B = #B
    local columns_B = #B[1]
    assert(
        columns_A == rows_B,
        string.format(
            [[
                Wrong size matrices for multiplication.
                Size A: %d,%d Size B: %d,%d
            ]],
            rows_A, columns_A,
            rows_B, columns_B
        )
    )
    local product = {}
    for row = 1, rows_A do
        product[row] = {}
        for column = 1, columns_B do
            product[row][column] = 0
            for dot_product_step = 1, columns_A do
                local a = A[row][dot_product_step]
                local b = B[dot_product_step][column]
                assert(type(a) == "number", 
                    string.format("Expected number but got %s in A[%d][%d]", type(a), row, dot_product_step))
                assert(type(b) == "number", 
                    string.format("Expected number but got %s in B[%d][%d]", type(b), dot_product_step, column))
                product[row][column] = product[row][column] + a * b
            end
        end
    end
    return product
end


local function zrotation(angle)
    local c = cos(angle)
    local s = sin(angle)
    return {
        {c,s,0,0}
        ,{-s,c,0,0}
        ,{0,0,1,0}
        ,{0,0,0,1}
    }
end

local function translate(x,y,z)
    return {
        {1,0,0,0}
        ,{0,1,0,0}
        ,{0,0,1,0}
        ,{x,y,z,1}
    }
end

local function scale(x,y,z)
    return {
        {x,0,0,0}
        ,{0,y,0,0}
        ,{0,0,z,0}
        ,{0,0,0,1}
    }
end


local function concat(...)
    local result = {}
    for _, t in ipairs{...} do
        for i = 1, #t do
            result[#result + 1] = t[i]
        end
    end
    return result
end

function mp.jasper_hilbert_curve_generate()
    local n = tonumber(metapost.getparameterset("order"))
    local curve = {
        {
            {0.5, 2,0,1},
            {0.5, 1.5,0,1}
        }
        ,{
            {0.5, 1.5,0,1},
            {0.5, 0.5,0,1}
        }
        ,{
            {0.5, 0.5,0,1},
            {1.5, 0.5,0,1}
        }
        ,{
            {1.5, 0.5,0,1},
            {1.5, 1.5,0,1}
        },{
            {1.5, 1.5,0,1},
            {2, 1.5,0,1}
        }
    }


    for k = 2, n do
        local s = (1/2)
        local t = 1--2^(k-1)
        local bottom_right = {}
        local top_right = {}
        local top_left = {}
        for i, v in ipairs(curve) do
            curve[i] = matrix_multiply(curve[i], scale(s,s,s))
        end

        for i, v in ipairs(curve) do

            bottom_right[i] = matrix_multiply(
                curve[i]
                ,matrix_multiply(
                    scale(-1,1,1)
                    ,translate(4*s*t,0,0)
                )
            )
            top_right[i] = matrix_multiply(
                curve[i]
                ,matrix_multiply(
                    zrotation(-math.pi/2)
                    ,translate(2*s*t,4*s*t,0)
                )
            )
        end
        for i, v in ipairs(curve) do
            top_left[i] = matrix_multiply(
                top_right[i]
                ,matrix_multiply(
                    zrotation(math.pi)
                    ,matrix_multiply(
                        scale(1,-1,1)
                        ,translate(4*s*t,0*s*t,0)
                    )
                )
            )
        end
        curve = concat(curve,bottom_right,top_right,top_left)
    end


    for i = 1, #curve do
        local A, B = curve[i][1], curve[i][2]
        mp.print(string.format(
            "path tmp ; tmp := (%f,%f) -- (%f,%f) ; draw tmp scaled 1cm ;",
            A[1], A[2], B[1], B[2]
        ))
    end

end
presetparameters "hilbert" [
    order = 3
] ;

def jasper_hilbert = applyparameters "hilbert" "jasper_do_hilbert" enddef ;

vardef jasper_do_hilbert =
    pushparameters "hilbert" ;
    lua.mp.jasper_hilbert_curve_generate() ;
    popparameters ;
enddef ;

output

1
  • 2
    +1. I've taken the liberty of adding meta-code to make the site software pretty-print the Lua code section differently. Feel free to revert. Commented Aug 3, 2025 at 0:35

1 Answer 1

2

I used the L-system approach from https://tex.stackexchange.com/a/749122/319072, but implemented in lua.

Had help from AI, so maybe someone could better critique it while I learn the structure better.

% jasper.tex
\ctxloadluafile{jasper.lua}
\processMPfigurefile{jasper.mp}
\starttext
  \dorecurse{10}{
    \startMPpage
      draw unitsquare scaled 5cm ;
      jasper_hilbert[order = #1] ;
    \stopMPpage
  }
\stoptext
-- jasper.lua
function mp.jasper_hilbert_curve_generate()
    local order = tonumber(metapost.getparameterset("order"))
    local step  = 1

    -- L-system rules
    local rules = {
        A = "+BX-AXA-XB+",
        B = "-AX+BXB+XA-"
    }

    -- expand axiom = "A"
    local function expand_axiom(axiom, order)
        local s = axiom
        for _ = 1, order do
            s = (s:gsub(".", function(c) return rules[c] or c end))
        end
        return s
    end

    local lsys = expand_axiom("A", order)

    -- turtle setup
    local t = { x = 0, y = 0, angle = 0 }
    local segs = {}
    local function forward()
        local x2 = t.x + step * math.cos(t.angle)
        local y2 = t.y + step * math.sin(t.angle)
        segs[#segs+1] = {
            { t.x,  t.y,  0, 1 },
            { x2,   y2,   0, 1 }
        }
        t.x, t.y = x2, y2
    end
    local function turn_right() t.angle = t.angle - math.pi/2 end
    local function turn_left()  t.angle = t.angle + math.pi/2 end

    -- interpret: draw only on X → keeps it a perfect square
    for c in lsys:gmatch(".") do
        if c == "X" then        -- ← only X, not B
            forward()
        elseif c == "+" then
            turn_right()
        elseif c == "-" then
            turn_left()
        end
    end


    -- compute bounding box
    local minx, miny = 1e9, 1e9
    local maxx, maxy = -1e9, -1e9
    for _, seg in ipairs(segs) do
        for _, pt in ipairs(seg) do
            minx = math.min(minx, pt[1])
            miny = math.min(miny, pt[2])
            maxx = math.max(maxx, pt[1])
            maxy = math.max(maxy, pt[2])
        end
    end

    local dx, dy = maxx - minx, maxy - miny
    -- add two half-step margins → total extra = 1*step
    local total_span = math.max(dx, dy) + step
    local s = 1 / total_span

    -- emit MetaPost, shifted by (-min + ½) and scaled to [0,1]
    for _, seg in ipairs(segs) do
        local A, B = seg[1], seg[2]
        local ax = (A[1] - minx + 0.5 * step) * s
        local ay = (A[2] - miny + 0.5 * step) * s
        local bx = (B[1] - minx + 0.5 * step) * s
        local by = (B[2] - miny + 0.5 * step) * s

        mp.print(string.format(
            "path tmp; tmp := (%f,%f) -- (%f,%f); draw tmp scaled 5cm;",
            ax, ay, bx, by
        ))
    end
end
presetparameters "hilbert" [
    order = 3
] ;

def jasper_hilbert = applyparameters "hilbert" "jasper_do_hilbert" enddef ;

vardef jasper_do_hilbert =
    pushparameters "hilbert" ;
    lua.mp.jasper_hilbert_curve_generate() ;
    popparameters ;
enddef ;

output

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.