function cycleTable(t, n)
  local i, v = lume.find(t, n)
  i = i or 1
  return t[i == #t and 1 or i + 1]
end

function capFirst(s)
  return string.upper(s:sub(1, 1)) .. s:sub(2)
end

function easeInOut(x, easeIn, easeOut)
  return x < 0.5 and ((x * 2) ^ (1 + easeIn)) / 2 or 1 - math.abs(x * 2 - 2) ^ (1 + easeOut) / 2
end

function colorTable(color)
  return color.r and { color.r, color.g, color.b, color.a or 1 } or { r = color[1], g = color[2], b = color[3], a = color[4] or 1 }
end

function rgb(c)
  return { r = c.r, g = c.g, b = c.b }
end

function easeTo(var, target, slow, dec)
  return round(target - var, dec or 2) == 0 and target or (var + (target - var) / (slow or 4))
end

function angDiff(a, b)
  local diff = (b - a + pi) % (pi * 2) - pi
  return diff < -pi and diff + pi * 2 or diff
end

function returnValue(val)
  return val
end

function delayedCall(func, time)
  local x = { y = 0 }
  scene_tween(x, { y = 1 }, time, "linear", 1, func)
end

function findString(str, s)
  local t = {} -- table to store the indices
  local i = 0
  while true do
    i = string.find(str, s, i + 1) -- find 'next' newline
    if i == nil then break end
    table.insert(t, i)
  end
end

function reversedipairsiter(t, i)
  i = i - 1
  if i ~= 0 then return i, t[i] end
end

function ripairs(t)
  return reversedipairsiter, t, #t + 1
end

function toArray(t, array)
  lume.clear(array)
  for k, v in pairs(t) do
    table.insert(array, v)
  end

  return array
end

function triangleArea(a, b, c)
  local s = (a + b + c) / 2
  return (s * (s - a) * (s - b) * (s - c)) ^ 0.5
end

function setTable(
  t,
  k1,
  v1,
  k2,
  v2,
  k3,
  v3,
  k4,
  v4,
  k5,
  v5,
  k6,
  v6,
  k7,
  v7,
  k8,
  v8,
  k9,
  v9,
  k10,
  v10,
  k11,
  v11,
  k12,
  v12,
  k13,
  v13,
  k14,
  v14,
  k15,
  v15,
  k16,
  v16
)
  if k1 then t[k1] = v1 end
  if k2 then t[k2] = v2 end
  if k3 then t[k3] = v3 end
  if k4 then t[k4] = v4 end
  if k5 then t[k5] = v5 end
  if k6 then t[k6] = v6 end
  if k7 then t[k7] = v7 end
  if k8 then t[k8] = v8 end
  if k9 then t[k9] = v9 end
  if k10 then t[k10] = v10 end
  if k11 then t[k11] = v11 end
  if k12 then t[k12] = v12 end
  if k13 then t[k13] = v13 end
  if k14 then t[k14] = v14 end
  if k15 then t[k15] = v15 end
  if k16 then t[k16] = v16 end
  return t
end

function wrap2(n, a, b)
  local wrapped = n < a and b - (a - n) + 1 or (n > b and a + (n - b) - 1 or n)
  return (wrapped < a or wrapped > b) and wrap2(wrapped, a, b) or wrapped
end

function wrap3(n, a, b)
  return ((n - a) % (b - a)) + a
end

function sortBy(t, ...)
  local props = { ... }
  local iter = {}
  for s, set in pairs(t) do
    table.insert(iter, set)
  end
  return lume.sort(iter, function(a, b)
    return sorter(a, b, props, 1)
  end)
end

function sorter(a, b, props, i)
  if a[props[i]] == b[props[i]] and i < #props then
    return sorter(a, b, props, i + 1)
  else
    if type(a[props[i]]) == "boolean" then
      return (not a[props[i]] and b[props[i]])
    else
      return a[props[i]] < b[props[i]]
    end
  end
end

function setVec(v, vx, vy, vz)
  local x, y, z
  if vy then
    x, y, z = vx, vy, vz
  else
    x, y, z = vx.x or vx[1], vx.y or vx[2], vx.z or vx[3]
  end
  if v.x then
    v.x, v.y, v.z = x, y, z
  else
    v[1], v[2], v[3] = x, y, z
  end
end

function clearTable(t, n, skipfuncs)
  n = (n or 0) + 1
  if n > 64 then return end
  for k, v in pairs(t) do
    local count = 0
    if type(v) == "table" then
      clearTable(v, n, skipfuncs)
      t[k] = nil
    elseif skipfuncs == false or type(v) ~= "function" then
      t[k] = nil
    end
  end
end

function copyArray(t)
  local copy = {}
  for i = 1, #t, 1 do
    table.insert(copy, t[i])
  end
  return copy
end

function clearTail(t, n)
  local n = n or 1
  for i = #t, n, -1 do
    table.remove(t, i)
  end
end

table.clear = function(t, n)
  for i = #t, n or 1, -1 do
    t[i] = nil
  end
end

function getLooped(t, n)
  return t[wrap2(n, 1, #t)]
end

function reverse(t)
  local rt = {}
  for i, v in lume.ripairs(t) do
    table.insert(rt, v)
  end
  return rt
end

function reverseInplace(t)
  local n = #t
  local i = 1
  while i < n do
    t[i], t[n] = t[n], t[i]
    i = i + 1
    n = n - 1
  end
  return t
end

function reverseInplace2(t)
  local n = t[0][0]
  local i = 1
  while i < n do
    t[i], t[n] = t[n], t[i]
    i = i + 1
    n = n - 1
  end
  return t
end

function addrem(t, v)
  local find = lume.find(t, v)
  if find then
    table.remove(t, find)
  else
    table.add(t, v)
  end
end

function bboxLine(line)
  local bbox = { line[1], line[2], line[1], line[2] }
  for i, b in ipairs(line) do
    if i % 2 == 0 then
      bbox[2] = (b < bbox[2]) and b or bbox[2]
      bbox[4] = (b > bbox[4]) and b or bbox[4]
    else
      bbox[1] = (b < bbox[1]) and b or bbox[1]
      bbox[3] = (b > bbox[3]) and b or bbox[3]
    end
  end
  bbox.w = bbox[3] - bbox[1]
  bbox.h = bbox[4] - bbox[2]
  return bbox
end

function pointInBB(p, b)
  local t1, l1 = p.x or p[1], p.y or p[2]
  local t2, l2, b2, r2 = b.t or b[1], b.l or b[2], b.b or b[3], b.r or b[4]
  return l1 < r2 and l2 < l1 and t1 < b2 and t2 < t1
end

function pointInBBarray(p, b)
  local t1, l1 = p.x or p[1], p.y or p[2]
  local t2, l2, b2, r2 = b[1], b[2], b[3], b[4]
  return l1 < r2 and l2 < l1 and t1 < b2 and t2 < t1
end

function frandom(x)
  return select(2, math.modf(fr(x + 10) - fr(x + 5) * 0.2))
end

function fr(x)
  return select(2, math.modf(math.sin(100 * x ^ 2) * math.cos(x ^ 4) * math.sin((8 * x) ^ 2) * math.cos(8 * x ^ 3) * 3 + 0.5))
end

function splice(t, first, last, t2)
  if first > #t then return end
  local t2 = t2 or {}
  for i = first or 1, last or #t, 1 do
    table.insert(t2, t[i])
  end
  return t2
end

function sling0(a, n, b, s)
  return easeTo(n, (n < 0) and 0 or ((n > b) and b or n), s)
end

function concat(t1, t2)
  for i = 1, #t2, 1 do
    t1[#t1 + 1] = t2[i]
  end
  return t1
end

function concat2(t1, t2)
  for i = 1, t2[0][0], 1 do
    t1[0][0] = t1[0][0] + 1
    t1[t1[0][0]] = t2[i]
  end
  return t1
end

function concatReverse(t1, t2)
  for i = #t2, 1, -1 do
    t1[#t1 + 1] = t2[i]
  end
end

function prepend(t1, t2)
  for i = #t2, 1, -1 do
    table.insert(t1, 1, t2[i])
  end
end

function classicConcat(t, t2, pre, fast)
  local pre = pre or false
  if pre then
    for i, v in ipairs(t2) do
      table.insert(t, 1, v)
    end
  else
    if fast then
      for i = 1, #t2, 1 do
        t[#t + 1] = t2[i]
      end
    else
      for i, v in ipairs(t2) do
        table.insert(t, v)
      end
    end
  end
  return t
end

function table.add(t, v)
  if not lume.find(t, v) then table.insert(t, v) end
end

function ellipses(string, width, always)
  if true then return string end
  local currentFont = gfx.currentFont
  local string = string or ""
  if currentFont and pcall(currentFont.getWidth, currentFont, string) and string:len() > 3 then
    local limit = 2
    local w
    for i = 1, string:len(), 1 do
      local thing = string:sub(1, i)
      if pcall(currentFont.getWidth, currentFont, thing) and currentFont:getWidth(thing) < width then limit = i end
    end
    return (string:sub(1, mc(1, limit, string:len())) or "") .. (string:len() > limit and "…" or (always and "…" or ""))
  else
    return string or ""
  end
  return string
end

function returnColor(t)
  return t.r, t.g, t.b
end

function contrast(col, n)
  local avg = (col.r + col.b + col.g) / 3
  local newCol = {
    r = col.r + (col.r - avg) * -(1 - n),
    g = col.g + (col.g - avg) * -(1 - n),
    b = col.b + (col.b - avg) * -(1 - n),
  }
  return newCol
end

function inTable(t, v)
  for i = 1, #t, 1 do
    if t[i] == v then return true end
  end
  return false
end

function mergeIndexed(t1, t2)
  --[[local t3 = shallowcopy(t1)
  for i,v in ipairs(t2) do
    if #t1==0 or not multicheckOR(v,t1) then
      table.insert(t3,v)
    end
  end]]
  local t3 = shallowcopy(t1)
  for i, v in ipairs(t2) do
    table.add(t3, v)
  end
  return t3
end

function simpleSame(a, b)
  if lume.count(a) ~= lume.count(b) then return false end
  for k, v in pairs(a) do
    if b[k] ~= v then return false end
  end
  return true
end

function multicheckOR(a, b, v) -- Returns true if value a is in table b
  local rval
  for _, m in pairs(b) do
    if m == a or (v ~= nil and type(m) == "table" and m[v] == a) then rval = _ end
  end
  return rval
end

function multicheckAND(a, b, v) -- Returns true if all values of table b equal a
  local rval = true
  for i = 1, #b do
    if b[i] ~= a then rval = false end
  end
  return rval
end

function roundToNearest(number, interval, offset)
  offset = offset or 0 -- default value
  interval = interval or 1 -- default value
  return math.floor((number - offset) / interval + 0.5) * interval + offset
end

function mean(t, val)
  local sum = 0
  local count = 0
  for k, v in pairs(t) do
    if val ~= nil and type(v[val]) == "number" then
      sum = sum + v[val]
      count = count + 1
    elseif type(v) == "number" then
      sum = sum + v
      count = count + 1
    end
  end
  return (sum / count)
end

function posmean(t, val)
  local sum = 0
  local count = 0
  for k, v in pairs(t) do
    if val ~= nil and type(v[val]) == "number" and v[val] > 0 then
      sum = sum + v[val]
      count = count + 1
    elseif type(v) == "number" and v > 0 then
      sum = sum + v
      count = count + 1
    end
  end
  return (sum / count)
end

function sum(t, val)
  local sum = 0

  for k, v in pairs(t) do
    if val and type(v[val]) == "number" then
      sum = sum + v[val]
    elseif type(v) == "number" then
      sum = sum + v
    end
  end
  return sum
end

function hex2rgb(hex, a)
  local a = a or 1
  local hex = hex:gsub("#", "")
  local col = {
    r = tonumber("0x" .. hex:sub(1, 2)),
    g = tonumber("0x" .. hex:sub(3, 4)),
    b = tonumber("0x" .. hex:sub(5, 6)),
    a = a * 255,
  }
  return col
end

function setColorHex(hexc, a)
  local a = a or 1
  local col = hex2rgb(hexc)
  gfx.setColor(col.r, col.g, col.b, 1)
end

function round(num, idp)
  local idp = idp or 2
  local mult = 10 ^ (idp or 2)
  return math.floor(num * mult + 0.5) / mult
end

function tableToString(t)
  local str = ""
  for i, v in ipairs(t) do
    str = str .. v
  end
  return str
end

function rmSpaces(string)
  local str = string:gsub("%s+", "")
  return str
end

function stringUUID(id)
  local id = id or simpleuuid()
  return ((id:sub(1, 1) ~= "u") and "u" or "") .. string.gsub(id, "-", "")
end

function numUUID(id)
  return tonumber(simpleuuid())
end

uuid = stringUUID

newFrameID = stringUUID

function stringFrameID(ID)
  return ID
end

function unpackColor(c)
  if type(c) == "table" then
    if c.r then
      return c.r, c.g, c.b, c.a
    else
      return c[1], c[2], c[3], c[4]
    end
  else
    return 1, 1, 1, 1
  end
end

function setAll(x, n)
  local t = {}
  for i = 1, n or 99, 1 do
    t[i] = x
  end
  return unpack(t)
end

function intersect(L1X1, L1Y1, L1X2, L1Y2, L2X1, L2Y1, L2X2, L2Y2)
  if (L1X1 == L2X1 and L1Y1 == L2Y1) or (L1X2 == L2X1 and L1Y2 == L2Y1) or (L1X1 == L2X2 and L1Y1 == L2Y2) or (L1X2 == L2X2 and L1Y2 == L2Y2) then
    return false
  end
  local d = (L2Y2 - L2Y1) * (L1X2 - L1X1) - (L2X2 - L2X1) * (L1Y2 - L1Y1)
  if d == 0 then
    return false
  else
    local ua = ((L2X2 - L2X1) * (L1Y1 - L2Y1) - (L2Y2 - L2Y1) * (L1X1 - L2X1)) / d
    local ub = ((L1X2 - L1X1) * (L1Y1 - L2Y1) - (L1Y2 - L1Y1) * (L1X1 - L2X1)) / d

    if ua >= 0 and ua <= 1 and ub >= 0 and ub <= 1 then return L1X1 + (ua * (L1X2 - L1X1)), L1Y1 + (ua * (L1Y2 - L1Y1)) end
    return false
  end
end

function copyVerts(verts)
  local t = {}
  for v, vert in ipairs(verts) do
    table.insert(t, vec3(vert.x, vert.y, vert.z))
  end
  return t
end

------------------------------------------------------------------------------
--[[
 * Converts an RGB color value to HSL. Conversion formula
 * adapted from http://en.wikipedia.org/wiki/HSL_color_space.
 * Assumes r, g, and b are contained in the set [0, 1] and
 * returns h, s, and l in the set [0, 1].
 *
 * @param   Number  r       The red color value
 * @param   Number  g       The green color value
 * @param   Number  b       The blue color value
 * @return  Array           The HSL representation
]]
function rgbToHsl(r, g, b, a)
  local a = 1
  r, g, b = r / 1, g / 1, b / 1

  local max, min = math.max(r, g, b), math.min(r, g, b)
  local h, s, l

  l = (max + min) / 2

  if max == min then
    h, s = 0, 0 -- achromatic
  else
    local d = max - min
    local s
    if l > 0.5 then
      s = d / (2 - max - min)
    else
      s = d / (max + min)
    end
    if max == r then
      h = (g - b) / d
      if g < b then h = h + 6 end
    elseif max == g then
      h = (b - r) / d + 2
    elseif max == b then
      h = (r - g) / d + 4
    end
    h = h / 6
  end

  return h, s, l, a or 1
end

function hslToRgb(h, s, l, a)
  local r, g, b
  local a = a or 1
  s = s or 1

  if s == 0 then
    r, g, b = l, l, l -- achromatic
  else
    function hue2rgb(p, q, t)
      if t < 0 then t = t + 1 end
      if t > 1 then t = t - 1 end
      if t < 1 / 6 then return p + (q - p) * 6 * t end
      if t < 1 / 2 then return q end
      if t < 2 / 3 then return p + (q - p) * (2 / 3 - t) * 6 end
      return p
    end

    local q
    if l < 0.5 then
      q = l * (1 + s)
    else
      q = l + s - l * s
    end
    local p = 2 * l - q

    r = hue2rgb(p, q, h + 1 / 3)
    g = hue2rgb(p, q, h)
    b = hue2rgb(p, q, h - 1 / 3)
  end

  return r * 1, g * 1, b * 1, a * 1
end

function rgbToHsv(r, g, b, a)
  r, g, b, a = r / 1, g / 1, b / 1, a and a / 1 or 1
  local max, min = math.max(r, g, b), math.min(r, g, b)
  local h, s, v
  v = max

  local d = max - min
  if max == 0 then
    s = 0
  else
    s = d / max
  end

  if max == min then
    h = 0 -- achromatic
  else
    if max == r then
      h = (g - b) / d
      if g < b then h = h + 6 end
    elseif max == g then
      h = (b - r) / d + 2
    elseif max == b then
      h = (r - g) / d + 4
    end
    h = h / 6
  end

  return h, s, v, a
end

function hsvToRgb(h, s, v, a)
  local r, g, b
  local a = a or 1
  local i = math.floor(h * 6)
  local f = h * 6 - i
  local p = v * (1 - s)
  local q = v * (1 - f * s)
  local t = v * (1 - (1 - f) * s)

  i = i % 6

  if i == 0 then
    r, g, b = v, t, p
  elseif i == 1 then
    r, g, b = q, v, p
  elseif i == 2 then
    r, g, b = p, v, t
  elseif i == 3 then
    r, g, b = p, q, v
  elseif i == 4 then
    r, g, b = t, p, v
  elseif i == 5 then
    r, g, b = v, p, q
  end

  return r * 1, g * 1, b * 1, a * 1
end

-- hsvToRgb = hslToRgb
-- rgbToHsv = rgbToHsl

------------------------------------------------------------------------

function catmull(points, steps)
  if #points < 3 then return points end

  local steps = steps or 5

  local spline = {}
  local count = #points - 1
  local p0, p1, p2, p3, x, y

  for i = 1, count do
    if i == 1 then
      p0, p1, p2, p3 = points[i], points[i], points[i + 1], points[i + 2]
    elseif i == count then
      p0, p1, p2, p3 = points[#points - 2], points[#points - 1], points[#points], points[#points]
    else
      p0, p1, p2, p3 = points[i - 1], points[i], points[i + 1], points[i + 2]
    end

    for t = 0, 1, 1 / steps do
      x = 0.5 * ((2 * p1.x) + (p2.x - p0.x) * t + (2 * p0.x - 5 * p1.x + 4 * p2.x - p3.x) * t * t + (3 * p1.x - p0.x - 3 * p2.x + p3.x) * t * t * t)
      y = 0.5 * ((2 * p1.y) + (p2.y - p0.y) * t + (2 * p0.y - 5 * p1.y + 4 * p2.y - p3.y) * t * t + (3 * p1.y - p0.y - 3 * p2.y + p3.y) * t * t * t)

      --prevent duplicate entries
      if not (#spline > 0 and spline[#spline].x == x and spline[#spline].y == y) then table.insert(spline, { x = x, y = y }) end
    end
  end

  return spline
end

local buffer = {}
function catmullLine(points, steps, temp, out)
  local n = 1
  if #points < 3 then return points, n end

  local steps = math.max(0, mf(steps) or 3) + 1
  if steps == 1 then return points, #points end

  local spline = temp and buffer or {}

  local count = #points - 1
  local p0, p1, p2, p3, x, y = vec2(), vec2(), vec2(), vec2()
  local add = false
  for i = 1, count, 2 do
    add = false
    if i == 1 then
      p0.x, p0.y = points[i], points[i + 1]
      p1.x, p1.y = points[i], points[i + 1]
      p2.x, p2.y = points[i + 2], points[i + 3]
      p3.x, p3.y = points[i + 4] or p2.x, points[i + 5] or p2.y
    elseif i == count then
      p0.x, p0.y = points[i - 4] or points[i - 2], points[i - 3] or points[i - 1]
      p1.x, p1.y = points[i - 2], points[i - 1]
      p2.x, p2.y = points[i], points[i + 1]
      p3.x, p3.y = points[i] or p2.x, points[i + 1] or p2.y
    else
      p0.x, p0.y = points[i - 2], points[i - 1]
      p1.x, p1.y = points[i], points[i + 1]
      p2.x, p2.y = points[i + 2], points[i + 3]
      p3.x, p3.y = points[i + 4] or p2.x, points[i + 5] or p2.y
    end

    for t = 0, 1, 1 / steps do
      x = 0.5 * ((2 * p1.x) + (p2.x - p0.x) * t + (2 * p0.x - 5 * p1.x + 4 * p2.x - p3.x) * t * t + (3 * p1.x - p0.x - 3 * p2.x + p3.x) * t * t * t)
      y = 0.5 * ((2 * p1.y) + (p2.y - p0.y) * t + (2 * p0.y - 5 * p1.y + 4 * p2.y - p3.y) * t * t + (3 * p1.y - p0.y - 3 * p2.y + p3.y) * t * t * t)

      if not (#spline > 0 and spline[#spline - 1] == x and spline[#spline] == y) then
        if temp then
          spline[n] = x
          n = n + 1
          spline[n] = y
          n = n + 1
        else
          table.insert(spline, x)
          table.insert(spline, y)
        end
      end
    end

    if temp then
      n = n - 2
    else
      if i < count then
        table.remove(spline, #spline)
        table.remove(spline, #spline)
      end
    end
  end
  
  return spline, n
end

--http://www.opengl-tutorial.org/intermediate-tutorials/tutorial-17-quaternions/#quaternions
function quatBetweenVecs(start, dest)
  start = start:normalize()
  dest = dest:normalize()

  local cosTheta = start:dot(dest)
  local rotationAxis = vec3()

  if cosTheta < -1 + 0.001 then
    rotationAxis = vec3.cross(vec3(0, 0, 1), start)
    if rotationAxis:len2() < 0.01 then rotationAxis = vec3.cross(vec3(1, 0, 0), start) end
    rotationAxis = rotationAxis:normalize()
    return quat.from_angle_axis(pi, rotationAxis)
  end

  rotationAxis = start:cross(dest)

  local s = math.sqrt((1 + cosTheta) * 2)
  local invs = 1 / s

  return quat(rotationAxis.x * invs, rotationAxis.y * invs, rotationAxis.z * invs, s * 0.5)
end

function recursivelyDelete(item)
  if lfs.exists(item) then
    if lfs.isDir(item) then
      for _, child in pairs(lfs.getDirectoryItems(item)) do
        recursivelyDelete(item .. "/" .. child.name)
        lfs.remove(item .. "/" .. child.name)
      end
    else
      lfs.remove(item)
    end
  end
end

function basename(str)
  local name = string.gsub(str, "(.*/)(.*)", "%2")
  return name
end

function simpleuuid()
  local template = "xxxxxxxxxxxxxxxx"
  return string.gsub(template, "[xy]", function(c)
    local v = (c == "x") and math.random(0, 0xf) or math.random(8, 0xb)
    return string.format("%x", v)
  end)
end

--https://gist.github.com/Xeoncross/9511295
function math.intersect(a, b, c, d, e, f, g, h, simp)
  -- parameter conversion
  local L1
  local L2

  if e then
    L1 = { X1 = a, Y1 = b, X2 = c, Y2 = d }
    L2 = { X1 = e, Y1 = f, X2 = g, Y2 = h }
  else
    L1 = { X1 = a.x, Y1 = a.y, X2 = b.x, Y2 = b.y }
    L2 = { X1 = c.x, Y1 = c.y, X2 = d.x, Y2 = d.y }
  end

  -- Denominator for ua and ub are the same, so store this calculation
  local d = (L2.Y2 - L2.Y1) * (L1.X2 - L1.X1) - (L2.X2 - L2.X1) * (L1.Y2 - L1.Y1)

  -- Make sure there is not a division by zero - this also indicates that the lines are parallel.
  -- If n_a and n_b were both equal to zero the lines would be on top of each
  -- other (coincidental).  This check is not done because it is not
  -- necessary for this implementation (the parallel check accounts for this).
  if d == 0 then return false end

  -- n_a and n_b are calculated as seperate values for readability
  local n_a = (L2.X2 - L2.X1) * (L1.Y1 - L2.Y1) - (L2.Y2 - L2.Y1) * (L1.X1 - L2.X1)
  local n_b = (L1.X2 - L1.X1) * (L1.Y1 - L2.Y1) - (L1.Y2 - L1.Y1) * (L1.X1 - L2.X1)

  -- Calculate the intermediate fractional point that the lines potentially intersect.
  local ua = n_a / d
  local ub = n_b / d

  -- The fractional point will be between 0 and 1 inclusive if the lines
  -- intersect.  If the fractional calculation is larger than 1 or smaller
  -- than 0 the lines would need to be longer to intersect.
  if ua >= 0 and ua <= 1 and ub >= 0 and ub <= 1 then
    local x = L1.X1 + (ua * (L1.X2 - L1.X1))
    local y = L1.Y1 + (ua * (L1.Y2 - L1.Y1))
    if simp then
      return true, x, y
    else
      return true, { x = x, y = y }
    end
  end

  return false
end

function simplePointInRect(point, rect)
  return (point.x > rect.x and point.x < rect.x + (rect.w or rect.width) and point.y > rect.y and point.y < rect.y + (rect.h or rect.height))
end
function pointInBBox(bbox, px, py)
  return pointInBox(px, py, bbox[1], bbox[2], bbox[3], bbox[4])
end
function pointInBox(px, py, rx1, ry1, rx2, ry2)
  return (px > rx1 and px < rx2 and py > ry1 and py < ry2)
end
function pointInBox2(px, py, x, y, w, h)
  return (px > x and px < x + w and py > y and py < y + h)
end

function bboxCollide(b1, b2) -- AABBCColliDDEEr
  local x1, y1, w1, h1 = b1[1], b1[2], b1[3], b1[4]
  local x2, y2, w2, h2 = b2[1], b2[2], b2[3], b2[4]
  return x1 < x2 + w2 and x2 < x1 + w1 and y1 < y2 + h2 and y2 < y1 + h1
end

function bboxCollide1(box1, box2) -- AABBCColliDDEEr
  local l1, t1, r1, b1 = box1[1], box1[2], box1[3], box1[4]
  local l2, t2, r2, b2 = box2[1], box2[2], box2[3], box2[4]
  return l1 < r2 and l2 < r1 and t1 < b2 and t2 < b1
end

function bboxCollide2(b1, b2) -- AABBCColliDDEEr
  return b1.x < b2.x + (b2.w or b2.width)
    and b2.x < b1.x + (b1.w or b1.width)
    and b1.y < b2.y + (b2.h or b2.height)
    and b2.y < b1.y + (b1.h or b1.height)
end

function lerp(a, b, t)
  if not a then print_info(5) end
  return (1 - t) * a + t * b
end
function lerpang(a, b, t)
  local a = a
  local b = b
  local dtheta = b - a
  if dtheta > pi then
    a = a + 2 * pi
  elseif dtheta < -pi then
    a = a - 2 * pi
  end
  return (1 - t) * a + t * b
end

function lerpAng(a, b, t)
  local result
  local diff = b - a
  if diff < -pi then
    -- lerp upwards past pi*2
    b = b + pi * 2
    result = lerp(a, b, t)
    if result >= pi * 2 then result = result - pi * 2 end
  elseif diff > pi then
    -- lerp downwards past 0
    b = b - pi * 2
    result = lerp(a, b, t)
    if result < 0 then result = result + pi * 2 end
  else
    -- straight lerp
    result = lerp(a, b, t)
  end
  return result
end

local sqrt = math.sqrt
local abs = math.abs
function distPointToLineSegment(pt, l1, l2, inf, signed)
  local dx, dy = l2[1] - l1[1], l2[2] - l1[2]
  local length = sqrt(dx * dx + dy * dy)
  dx, dy = dx / length, dy / length
  local posOnLine = dx * (pt[1] - l1[1]) + dy * (pt[2] - l1[2])
  if not inf and posOnLine < 0 then
    dx, dy = pt[1] - l1[1], pt[2] - l1[2]
    return sqrt(dx * dx + dy * dy)
  elseif not inf and posOnLine > length then
    dx, dy = pt[1] - l2[1], pt[2] - l2[2]
    return sqrt(dx * dx + dy * dy)
  else
    if signed then
      return (dy * (pt[1] - l1[1]) - dx * (pt[2] - l1[2]))
    else
      return abs(dy * (pt[1] - l1[1]) - dx * (pt[2] - l1[2]))
    end
  end
end
function distPointToLineSegment2(ptx, pty, l1x, l1y, l2x, l2y, inf, signed)
  local dx, dy = l2x - l1x, l2y - l1y
  local length = sqrt(dx * dx + dy * dy)
  dx, dy = dx / length, dy / length
  local posOnLine = dx * (ptx - l1x) + dy * (pty - l1y)
  if not inf and posOnLine < 0 then
    dx, dy = ptx - l1x, pty - l1y
    return sqrt(dx * dx + dy * dy)
  elseif not inf and posOnLine > length then
    dx, dy = ptx - l2x, pty - l2y
    return sqrt(dx * dx + dy * dy)
  else
    if signed then
      return (dy * (ptx - l1x) - dx * (pty - l1y))
    else
      return abs(dy * (ptx - l1x) - dx * (pty - l1y))
    end
  end
end

local function det(x1, y1, x2, y2)
  return x1 * y2 - y1 * x2
end
local function onSameSide(a, b, c, d)
  local px, py = d[1] - c[1], d[2] - c[2]
  local l = det(px, py, a[1] - c[1], a[2] - c[2])
  local m = det(px, py, b[1] - c[1], b[2] - c[2])
  return l * m >= 0
end
local function onSameSide2(ax, ay, bx, by, cx, cy, dx, dy, e)
  local px, py = dx - cx, dy - cy
  local l = det(px, py, ax - cx, ay - cy)
  local m = det(px, py, bx - cx, by - cy)
  return l * m >= e
end

function pointInTri(tri, px, py, e)
  local e = e or 0
  return onSameSide2(px, py, tri[1], tri[2], tri[3], tri[4], tri[5], tri[6], e)
    and onSameSide2(px, py, tri[3], tri[4], tri[1], tri[2], tri[5], tri[6], e)
    and onSameSide2(px, py, tri[5], tri[6], tri[1], tri[2], tri[3], tri[4], e)
end

function point_in_triangle(a, b, c, p) -- Harden Collider approach
  return onSameSide(p, a, b, c) and onSameSide(p, b, a, c) and onSameSide(p, c, a, b)
end

local function crossingsMultiplyTest(pgon, tx, ty)
  local i, yflag0, yflag1, inside_flag
  local vtx0, vtx1
  local tab = type(pgon[1]) == "table" or type(pgon[1]) == "cdata"
  local len = type(pgon[1]) == "cdata" and pgon[0][0] or #pgon

  if tab then
    vtx0 = vec2(pgon[len][1], pgon[len][2])
    vtx1 = vec2(pgon[1][1], pgon[1][2])
  else
    vtx0 = vec2(pgon[len - 1], pgon[len])
    vtx1 = vec2(pgon[1], pgon[2])
  end

  -- get test bit for above/below X axis
  yflag0 = (vtx0.y >= ty)
  inside_flag = false

  local first = tab and 2 or 3
  local last = tab and len + 1 or len + 2
  local inc = tab and 1 or 2
  for i = first, last, inc do
    yflag1 = (vtx1.y >= ty)

    if yflag0 ~= yflag1 then
      if ((vtx1.y - ty) * (vtx0.x - vtx1.x) >= (vtx1.x - tx) * (vtx0.y - vtx1.y)) == yflag1 then inside_flag = not inside_flag end
    end

    -- Move to the next pair of vertices, retaining info as possible.
    yflag0 = yflag1
    vtx0:set(vtx1)
    if tab then
      if i < len then vtx1:set(pgon[i][1], pgon[i][2]) end
    else
      if i < len then vtx1:set(pgon[i], pgon[i + 1]) end
    end
  end

  return inside_flag
end

local _tri, _point = { {}, {}, {} }, {}
local tempShape = ffi.new("float[10000][6]")
function point_in_poly(poly, point, prt)
  -- return PointWithinShape(shape, point.x,point.y, true)
  return crossingsMultiplyTest(poly, point.x, point.y)
end

function shallowcopy(orig)
  local orig_type = type(orig)
  local copy
  if orig_type == "table" and not orig.nocopy and not mat4.is_mat4(orig) then
    copy = {}
    for orig_key, orig_value in pairs(orig) do
      copy[shallowcopy(orig_key)] = shallowcopy(orig_value)
    end
  else -- number, string, boolean, etc
    copy = orig
  end
  return copy
end

function deepcopy(orig, exclude)
  local orig_type = type(orig)
  local copy
  local depth = depth or ""

  if
    orig_type == "table"
    and not (orig.isMesh or orig.isCanvas)
    and (not mat4.is_mat4(orig))
    and not orig.nocopy
    and not (orig[1] and orig[1] == "nocopy")
  then
    copy = {}
    for orig_key, orig_value in next, orig, nil do
      if
        (orig_key ~= "projMatrix")
        and (orig_key ~= "viewMatrix")
        and (orig_key ~= "transform")
        and not (exclude and lume.find(exclude, orgi_key))
      then
        copy[orig_key] = deepcopy(orig_value)
      end
    end
    setmetatable(copy, deepcopy(getmetatable(orig)))
  elseif orig_type ~= "userdata" then -- number, string, boolean, etc
    copy = orig
  end

  return copy
end

function HSV(h, s, v)
  if s <= 0 then return v, v, v end
  h, s, v = h / 256 * 6, s / 255, v / 255
  local c = v * s
  local x = (1 - math.abs((h % 2) - 1)) * c
  local m, r, g, b = (v - c), 0, 0, 0
  if h < 1 then
    r, g, b = c, x, 0
  elseif h < 2 then
    r, g, b = x, c, 0
  elseif h < 3 then
    r, g, b = 0, c, x
  elseif h < 4 then
    r, g, b = 0, x, c
  elseif h < 5 then
    r, g, b = x, 0, c
  else
    r, g, b = c, 0, x
  end
  return (r + m) * 255, (g + m) * 255, (b + m) * 255
end

function planeFromPoints(points)
  if #points < 3 then return end

  local sum = vec3()
  for _, p in ipairs(points) do
    sum.x = sum.x + p.x
    sum.y = sum.y + p.y
    sum.z = sum.z + p.z
  end
  local position = sum / #points

  -- Calc full 3x3 covariance matrix, excluding symmetries:
  local xx, xy, xz = 0, 0, 0
  local yy, yz, zz = 0, 0, 0

  for _, p in ipairs(points) do
    local r = p - position
    xx = xx + r.x * r.x
    xy = xy + r.x * r.y
    xz = xz + r.x * r.z
    yy = yy + r.y * r.y
    yz = yz + r.y * r.z
    zz = zz + r.z * r.z
  end

  local det_x = yy * zz - yz * yz
  local det_y = xx * zz - xz * xz
  local det_z = xx * yy - xy * xy

  local det_max = math.max(det_x, math.max(det_y, det_z))
  if det_max <= 0 then
    return -- The points don't span a 
    
  end

  -- Pick path with best conditioning:
  local dir = (det_max == det_x) and vec3(det_x, xz * yz - xy * zz, xy * yz - xz * yy)
    or ((det_max == det_y) and vec3(xz * yz - xy * zz, det_y, xy * xz - yz * xx) or vec3(xy * yz - xz * yy, xy * xz - yz * xx, det_z))

  return position, dir:normalize()
end

function PointWithinShape(shape, tx, ty, pn2)
  local len = type(shape) == "cdata" and shape[0][0] or #shape
  if len == 0 then
    return false
  elseif len == 1 then
    return false
  elseif len == 2 then
    return false
  else
    if type(shape) == "table" then
      return crossingsMultiplyTest(shape, tx, ty)
    else
      if pn2 then
        local sum = ffi.C.pnpoly2(shape[0][0], shape, tx, ty)
        return sum % 2 ~= 0
      else
        local sum = ffi.C.pnpoly(shape[0][0], shape, tx, ty)
        return sum % 2 ~= 0
      end
    end
  end
end
