-- "Extended" math module for Lunatic.

local ffi = require("ffi")

local bit = require("bit")
local math = require("math")

local geom = require("geom")

local assert = assert


module(...)


local BANG2RAD = math.pi/1024
local isintab = ffi.new("int16_t [?]", 2048)
local dsintab = ffi.new("double [?]", 2048)

for a=0,511 do
    local s = math.sin(a*BANG2RAD)
    isintab[a] = 16384*s
    dsintab[a] = s
end

isintab[512] = 16384
dsintab[512] = 1

for i=513,1023 do
    isintab[i] = isintab[1024-i];
    dsintab[i] = dsintab[1024-i];
end

for i=1024,2047 do
    isintab[i] = -isintab[i-1024];
    dsintab[i] = -dsintab[i-1024];
end


local band = bit.band

local function ksc_common(ang)
    ang = band(ang, 2047)
    assert(ang < 2048+0ULL)  -- might have been passed NaN
    return ang
end

-- k{sin,cos}: 16384-scaled output, 2048-based angle input
function ksin(ang)
    return isintab[ksc_common(ang)]
end

function kcos(ang)
    return isintab[ksc_common(ang+512)]
end


local sin, cos = math.sin, math.cos

-- {sin,cos}b: [-1..1] output, 2048-based angle input
function sinb(ang)
    return dsintab[ksc_common(ang)]
end

function cosb(ang)
    return dsintab[ksc_common(ang+512)]
end


-- Approximations to 2D and 3D Euclidean distances (also see common.c)
local abs = math.abs
local arshift = bit.arshift

local function dist_common(pos1, pos2)
    local x = abs(pos1.x - pos2.x)
    local y = abs(pos1.y - pos2.y)
    if (x < y) then
        x, y = y, x
    end
    return x, y
end

function ldist(pos1, pos2)
    local x, y = dist_common(pos1, pos2)

    local t = y + arshift(y,1)
    return x - arshift(x,5) - arshift(x,7) + arshift(t,2) + arshift(t,6)
end

function dist(pos1, pos2)
    local x, y = dist_common(pos1, pos2)
    local z = abs(arshift(pos1.z - pos2.z, 4))

    if (x < z) then
        x, z = z, x
    end

    local t = y + z
    return x - arshift(x,4) + arshift(t,2) + arshift(t,3)
end


-- Point rotation. Note the different order of arguments from engine function.
-- XXX: passing mixed vec2/vec3 is problematic. Get rid of geom.vec2?
-- <ang>: BUILD angle (0-2047 based)
function rotate(pos, pivot, ang)
    local p = geom.vec3(pos)-pivot
    local c, s = cosb(ang), sinb(ang)
    local x, y = p.x, p.y
    p.x = pivot.x + (c*x - s*y)
    p.y = pivot.y + (c*y + s*x)
    return p
end
