login

local ffi = require "ffi"

ffi.cdef[[
double hypot(double x, double y);
]]
local hypot = ffi.C.hypot

local function dashed(points, pattern)
    local x0, y0, x1, y1
    local cx, cy
    local i, j
    local d, h
    local polylines = {}
    local polyline
    local draw = true
    x0, y0 = unpack(points[1])
    polyline = {{x0, y0}}
    i = 2
    x1, y1 = unpack(points[i])
    j = 1
    d = pattern[j]
    while true do
        h = hypot(x1-x0, y1-y0)
        if d < h then
            cx = x0 + (x1-x0)*d/h
            cy = y0 + (y1-y0)*d/h
            if draw then
                table.insert(polyline, {cx, cy})
                table.insert(polylines, polyline)
            else
                polyline = {{cx, cy}}
            end
            x0, y0 = cx, cy
            draw = not draw
            if j < #pattern then j = j + 1 else j = 1 end
            d = pattern[j]
        else
            if draw then
                table.insert(polyline, {x1, y1})
            end
            d = d - h
            if i < #points then i = i + 1 else break end
            x0, y0 = x1, y1
            x1, y1 = unpack(points[i])
        end
    end
    if draw then
        table.insert(polyline, points[i])
        table.insert(polylines, polyline)
    end
    return polylines
end

-- convert bezier curve {{ax, ay}, {bx, by}, {cx, cy}} to polyline
local function bezier(curve)
    local h
    local dx, dy, ex, ey, fx, fy
    local a, b, c = unpack(curve)
    local ax, ay = unpack(a)
    local bx, by = unpack(b)
    local cx, cy = unpack(c)
    local points = {{ax, ay}}
    local stack = {curve}
    while #stack > 0 do
        a, b, c = unpack(table.remove(stack))
        ax, ay = unpack(a)
        bx, by = unpack(b)
        cx, cy = unpack(c)
        h = math.abs((ax-cx)*(by-ay)-(ax-bx)*(cy-ay))/hypot(cx-ax, cy-ay)
        if h > 1 then -- split curve
            dx, dy = (ax+bx)/2, (ay+by)/2
            fx, fy = (bx+cx)/2, (by+cy)/2
            ex, ey = (dx+fx)/2, (dy+fy)/2
            table.insert(stack, {{ex, ey}, {fx, fy}, {cx, cy}})
            table.insert(stack, {{ax, ay}, {dx, dy}, {ex, ey}})
        else -- add point to polyline
            table.insert(points, {cx, cy})
        end
    end
    return points
end

return {dashed=dashed, bezier=bezier}