local libphx = require('libphx').lib

function onDef_Math(t, mt)
    t.Tau           = 6.283185307179586232
    t.Pi            = 3.141592653589793116
    t.Pi2           = 1.570796326794896558
    t.Pi4           = 0.785398163397448279
    t.Pi6           = 0.52359877559829881566
    t.Infinity      = math.huge

    t.Float32MaxInt = 16777216         -- 2^24
    t.Float64MaxInt = 9007199254740992 -- 2^53

    t.ToRadians     = rad
    t.ToDegrees     = deg

    t.Approx        = function(a, b)
        return abs(a - b) < 1e-4
    end

    t.Avg           = function(a, b)
        return (a + b) / 2.0
    end

    t.DivAndMod     = function(x, divisor)
        local div = floor(x / divisor)
        local mod = x - div * divisor
        return div, mod
    end

    -- Exponential moving average
    t.EMA           = function(last, current, dt, period)
        local factor = exp(-dt / period)
        return factor * last + (1.0 - factor) * current
    end

    t.GeomAvg       = function(a, b)
        return (sqrt(a * b))
    end

    t.Lerp          = function(a, b, t)
        return a + (b - a) * t
    end

    t.LerpSnap      = function(a, b, t)
        local r = a + (b - a) * t
        if abs(a - r) < 1e-3 then r = a end
        if abs(b - r) < 1e-3 then r = b end
        return r
    end

    -- Compute projectile impact time and point given position and velocity of
    -- source / destination + closing speed of projectile. Assumes projectile
    -- inherits the velocity of source (in addition to the closing speed).
    -- Returns impact_time, impact_point or nil if no solution exists
    t.Impact        = function(pSrc, pDst, vSrc, vDst, speed)
        local dp = pDst - pSrc
        local dv = vDst - vSrc
        local a = dv:lengthSquared() - speed * speed
        local b = 2.0 * dv:dot(dp)
        local c = dp:lengthSquared()
        local d = b * b - 4.0 * a * c
        if d <= 0 then return nil end
        d = sqrt(d)
        local t = max((-b - d) / (2.0 * a), (-b + d) / 2.0 * a)
        return t, pDst + dv:scale(t)
    end

    t.InverseLerp   = function(a, b, v)
        return (v - a) / (b - a)
    end

    t.OrthoVector   = function(e1)
        local absY = abs(e1.y)
        if absY < 0.5 then
            return Vec3f(e1.z, 0, -e1.x):normalize()
        else
            return Vec3f(e1.y, -e1.x, 0):normalize()
        end
    end

    t.OrthoBasis    = function(e1)
        local e2 = Math.OrthoVector(e1)
        local e3 = e1:cross(e2):normalize()
        return e2, e3
    end

    t.IsInfinite    = function(x)
        return x == math.huge or x == -math.huge
    end

    t.IsNaN         = function(x)
        return x ~= x
    end

    t.IsOk          = function(x)
        return not Math.IsInfinite(x) and not Math.IsNaN(x)
    end

    t.AbsMax        = function(a, b)
        return abs(b) > abs(a) and b or a
    end

    -- Smoothstep 3rd order ('Smoothstep')
    -- 3x^2 - 2x^3
    t.SmoothStep3   = function(x)
        return x * x * (3 - 2 * x)
    end

    -- Smoothstep 5th order ('Smootherstep')
    -- 6x^5 - 15x^4 + 10x^3
    t.SmoothStep5   = function(x)
        return x * x * x * (10 + x * (-15 + 6 * x))
    end

    -- Smoothstep 7th order ('Smootheststep')
    -- -20x^7 + 70x^6 - 84x^5 + 35x^4
    t.SmoothStep7   = function(x)
        return x * x * x * x * (35 + x * (-84 + x * (70 - 20 * x)))
    end

    t.Round         = function(x)
        return floor(x + 0.5)
    end

    t.Saturate      = function(x)
        return max(0.0, min(1.0, x))
    end

    t.Sign          = function(x)
        return (x >= 0 and 1) or -1
    end

    t.Sign0         = function(x)
        return (x > 0 and 1) or (x < 0 and -1) or 0
    end

    t.Snap          = function(x, a, b, epsilon)
        if abs(x - a) < epsilon then return a end
        if abs(x - b) < epsilon then return b end
        return x
    end

    t.Spherical     = function(radius, pitch, yaw)
        return (Vec3f(
            radius * cos(pitch) * cos(yaw),
            radius * sin(pitch),
            radius * cos(pitch) * sin(yaw)))
    end

    -- WARNING : Integers only! Floats will wrap incorrectly.
    t.Wrap          = function(x, min, max)
        -- NOTE : Lua's integer mod function wraps negative back to positive,
        --        hence the following simple formula works:
        --           wrapped = offset % range + min
        return (x - min) % (max - min + 1) + min
    end
end
