]> git.mcshandy.xyz Git - sumeriangame/commitdiff
Add windfield for collision and lovebird for debugging
authorLobster <kevmuri14@gmail.com>
Wed, 14 Jan 2026 00:26:43 +0000 (19:26 -0500)
committerLobster <kevmuri14@gmail.com>
Wed, 14 Jan 2026 00:26:43 +0000 (19:26 -0500)
main/main.lua
main/plugin/STI [new submodule]
main/plugin/lovebird.lua [new file with mode: 0644]
main/plugin/windfield/init.lua [new file with mode: 0644]
main/plugin/windfield/mlib/Changes.txt [new file with mode: 0644]
main/plugin/windfield/mlib/LICENSE.md [new file with mode: 0644]
main/plugin/windfield/mlib/README.md [new file with mode: 0644]
main/plugin/windfield/mlib/mlib.lua [new file with mode: 0644]

index 135dabd08f5c2e69c180ba4523647d20df99fc2f..14a59207ce20f2d6c81ef0f6ed7dd8b24b666e30 100644 (file)
@@ -1,6 +1,11 @@
 -- main.lua
 
+-- Plugins
 local sti  = require('plugin/sti')
+local wf = require('plugin/windfield')
+local lovebird = require('plugin/lovebird')
+
+-- Modules
 local conf = require('conf')
 local assets = require('assets')
 local utils = require('utils')
@@ -8,9 +13,11 @@ local vector = require('vector')
 
 local active_map = nil
 local world = nil
-local player = nil
+player = nil
 local tx, ty
 
+local world = wf.newWorld(0, 0)
+
 function love.load()
                love.graphics.setFont(assets.get_font('Cuneiform36'))
     love.audio.play(assets.get_source('intro'))
@@ -36,7 +43,10 @@ function love.load()
 end
 
 function love.update(dt)
+  lovebird.update()
+
   active_map:update(dt)
+  world:update(dt)
 end
 
 function love.keypressed(key)
@@ -49,4 +59,5 @@ end
 function love.draw()
   active_map:draw(tx, ty, 2.0)
   love.graphics.print(IntroMessage, math.floor((conf.window.width/16) * 1), math.floor((conf.window.height/16) * 1))
+  world:draw()
 end
diff --git a/main/plugin/STI b/main/plugin/STI
new file mode 160000 (submodule)
index 0000000..a83eb64
--- /dev/null
@@ -0,0 +1 @@
+Subproject commit a83eb64db2db55e85205f15013eb6e7327be605d
diff --git a/main/plugin/lovebird.lua b/main/plugin/lovebird.lua
new file mode 100644 (file)
index 0000000..b11404a
--- /dev/null
@@ -0,0 +1,737 @@
+--
+-- lovebird
+--
+-- Copyright (c) 2017 rxi
+--
+-- This library is free software; you can redistribute it and/or modify it
+-- under the terms of the MIT license. See LICENSE for details.
+--
+
+local socket = require "socket"
+
+local lovebird = { _version = "0.4.3" }
+
+lovebird.loadstring = loadstring or load
+lovebird.inited = false
+lovebird.host = "*"
+lovebird.buffer = ""
+lovebird.lines = {}
+lovebird.connections = {}
+lovebird.pages = {}
+
+lovebird.wrapprint = true
+lovebird.timestamp = true
+lovebird.allowhtml = false
+lovebird.echoinput = true
+lovebird.port = 8000
+lovebird.whitelist = { "127.0.0.1" }
+lovebird.maxlines = 200
+lovebird.updateinterval = .5
+
+
+lovebird.pages["index"] = [[
+<?lua
+-- Handle console input
+if req.parsedbody.input then
+  local str = req.parsedbody.input
+  if lovebird.echoinput then
+    lovebird.pushline({ type = 'input', str = str })
+  end
+  if str:find("^=") then
+    str = "print(" .. str:sub(2) .. ")"
+  end
+  xpcall(function() assert(lovebird.loadstring(str, "input"))() end,
+         lovebird.onerror)
+end
+?>
+
+<!doctype html>
+<html>
+  <head>
+  <meta http-equiv="x-ua-compatible" content="IE=Edge"/>
+  <meta charset="utf-8">
+  <title>lovebird</title>
+  <style>
+    body {
+      margin: 0px;
+      font-size: 14px;
+      font-family: helvetica, verdana, sans;
+      background: #FFFFFF;
+    }
+    form {
+      margin-bottom: 0px;
+    }
+    .timestamp {
+      color: #909090;
+      padding-right: 4px;
+    }
+    .repeatcount {
+      color: #F0F0F0;
+      background: #505050;
+      font-size: 11px;
+      font-weight: bold;
+      text-align: center;
+      padding-left: 4px;
+      padding-right: 4px;
+      padding-top: 0px;
+      padding-bottom: 0px;
+      border-radius: 7px;
+      display: inline-block;
+    }
+    .errormarker {
+      color: #F0F0F0;
+      background: #8E0000;
+      font-size: 11px;
+      font-weight: bold;
+      text-align: center;
+      border-radius: 8px;
+      width: 17px;
+      padding-top: 0px;
+      padding-bottom: 0px;
+      display: inline-block;
+    }
+    .greybordered {
+      margin: 12px;
+      background: #F0F0F0;
+      border: 1px solid #E0E0E0;
+      border-radius: 3px;
+    }
+    .inputline {
+      font-family: mono, courier;
+      font-size: 13px;
+      color: #606060;
+    }
+    .inputline:before {
+      content: '\00B7\00B7\00B7';
+      padding-right: 5px;
+    }
+    .errorline {
+      color: #8E0000;
+    }
+    #header {
+      background: #101010;
+      height: 25px;
+      color: #F0F0F0;
+      padding: 9px
+    }
+    #title {
+      float: left;
+      font-size: 20px;
+    }
+    #title a {
+      color: #F0F0F0;
+      text-decoration: none;
+    }
+    #title a:hover {
+      color: #FFFFFF;
+    }
+    #version {
+      font-size: 10px;
+    }
+    #status {
+      float: right;
+      font-size: 14px;
+      padding-top: 4px;
+    }
+    #main a {
+      color: #000000;
+      text-decoration: none;
+      background: #E0E0E0;
+      border: 1px solid #D0D0D0;
+      border-radius: 3px;
+      padding-left: 2px;
+      padding-right: 2px;
+      display: inline-block;
+    }
+    #main a:hover {
+      background: #D0D0D0;
+      border: 1px solid #C0C0C0;
+    }
+    #console {
+      position: absolute;
+      top: 40px; bottom: 0px; left: 0px; right: 312px;
+    }
+    #input {
+      position: absolute;
+      margin: 10px;
+      bottom: 0px; left: 0px; right: 0px;
+    }
+    #inputbox {
+      width: 100%;
+      font-family: mono, courier;
+      font-size: 13px;
+    }
+    #output {
+      overflow-y: scroll;
+      position: absolute;
+      margin: 10px;
+      line-height: 17px;
+      top: 0px; bottom: 36px; left: 0px; right: 0px;
+    }
+    #env {
+      position: absolute;
+      top: 40px; bottom: 0px; right: 0px;
+      width: 300px;
+    }
+    #envheader {
+      padding: 5px;
+      background: #E0E0E0;
+    }
+    #envvars {
+      position: absolute;
+      left: 0px; right: 0px; top: 25px; bottom: 0px;
+      margin: 10px;
+      overflow-y: scroll;
+      font-size: 12px;
+    }
+  </style>
+  </head>
+  <body>
+    <div id="header">
+      <div id="title">
+        <a href="https://github.com/rxi/lovebird">lovebird</a>
+        <span id="version"><?lua echo(lovebird._version) ?></span>
+      </div>
+      <div id="status"></div>
+    </div>
+    <div id="main">
+      <div id="console" class="greybordered">
+        <div id="output"> <?lua echo(lovebird.buffer) ?> </div>
+        <div id="input">
+          <form method="post"
+                onkeydown="return onInputKeyDown(event);"
+                onsubmit="onInputSubmit(); return false;">
+            <input id="inputbox" name="input" type="text"
+                autocomplete="off"></input>
+          </form>
+        </div>
+      </div>
+      <div id="env" class="greybordered">
+        <div id="envheader"></div>
+        <div id="envvars"></div>
+      </div>
+    </div>
+    <script>
+      document.getElementById("inputbox").focus();
+
+      var changeFavicon = function(href) {
+        var old = document.getElementById("favicon");
+        if (old) document.head.removeChild(old);
+        var link = document.createElement("link");
+        link.id = "favicon";
+        link.rel = "shortcut icon";
+        link.href = href;
+        document.head.appendChild(link);
+      }
+
+      var truncate = function(str, len) {
+        if (str.length <= len) return str;
+        return str.substring(0, len - 3) + "...";
+      }
+
+      var geturl = function(url, onComplete, onFail) {
+        var req = new XMLHttpRequest();
+        req.onreadystatechange = function() {
+          if (req.readyState != 4) return;
+          if (req.status == 200) {
+            if (onComplete) onComplete(req.responseText);
+          } else {
+            if (onFail) onFail(req.responseText);
+          }
+        }
+        url += (url.indexOf("?") > -1 ? "&_=" : "?_=") + Math.random();
+        req.open("GET", url, true);
+        req.send();
+      }
+
+      var divContentCache = {}
+      var updateDivContent = function(id, content) {
+        if (divContentCache[id] != content) {
+          document.getElementById(id).innerHTML = content;
+          divContentCache[id] = content
+          return true;
+        }
+        return false;
+      }
+
+      var onInputSubmit = function() {
+        var b = document.getElementById("inputbox");
+        var req = new XMLHttpRequest();
+        req.open("POST", "/", true);
+        req.send("input=" + encodeURIComponent(b.value));
+        /* Do input history */
+        if (b.value && inputHistory[0] != b.value) {
+          inputHistory.unshift(b.value);
+        }
+        inputHistory.index = -1;
+        /* Reset */
+        b.value = "";
+        refreshOutput();
+      }
+
+      /* Input box history */
+      var inputHistory = [];
+      inputHistory.index = 0;
+      var onInputKeyDown = function(e) {
+        var key = e.which || e.keyCode;
+        if (key != 38 && key != 40) return true;
+        var b = document.getElementById("inputbox");
+        if (key == 38 && inputHistory.index < inputHistory.length - 1) {
+          /* Up key */
+          inputHistory.index++;
+        }
+        if (key == 40 && inputHistory.index >= 0) {
+          /* Down key */
+          inputHistory.index--;
+        }
+        b.value = inputHistory[inputHistory.index] || "";
+        b.selectionStart = b.value.length;
+        return false;
+      }
+
+      /* Output buffer and status */
+      var refreshOutput = function() {
+        geturl("/buffer", function(text) {
+          updateDivContent("status", "connected &#9679;");
+          if (updateDivContent("output", text)) {
+            var div = document.getElementById("output");
+            div.scrollTop = div.scrollHeight;
+          }
+          /* Update favicon */
+          changeFavicon("data:image/png;base64," +
+"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAP1BMVEUAAAAAAAAAAAD////19fUO"+
+"Dg7v7+/h4eGzs7MlJSUeHh7n5+fY2NjJycnGxsa3t7eioqKfn5+QkJCHh4d+fn7zU+b5AAAAAnRS"+
+"TlPlAFWaypEAAABRSURBVBjTfc9HDoAwDERRQ+w0ern/WQkZaUBC4e/mrWzppH9VJjbjZg1Ii2rM"+
+"DyR1JZ8J0dVWggIGggcEwgbYCRbuPRqgyjHNpzUP+39GPu9fgloC5L9DO0sAAAAASUVORK5CYII="
+          );
+        },
+        function(text) {
+          updateDivContent("status", "disconnected &#9675;");
+          /* Update favicon */
+          changeFavicon("data:image/png;base64," +
+"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAYFBMVEUAAAAAAAAAAADZ2dm4uLgM"+
+"DAz29vbz8/Pv7+/h4eHIyMiwsLBtbW0lJSUeHh4QEBDn5+fS0tLDw8O0tLSioqKfn5+QkJCHh4d+"+
+"fn5ycnJmZmZgYGBXV1dLS0tFRUUGBgZ0He44AAAAAnRSTlPlAFWaypEAAABeSURBVBjTfY9HDoAw"+
+"DAQD6Z3ey/9/iXMxkVDYw0g7F3tJReosUKHnwY4pCM+EtOEVXrb7wVRA0dMbaAcUwiVeDQq1Jp4a"+
+"xUg5kE0ooqZu68Di2Tgbs/DiY/9jyGf+AyFKBAK7KD2TAAAAAElFTkSuQmCC"
+          );
+        });
+      }
+      setInterval(refreshOutput,
+                  <?lua echo(lovebird.updateinterval) ?> * 1000);
+
+      /* Environment variable view */
+      var envPath = "";
+      var refreshEnv = function() {
+        geturl("/env.json?p=" + envPath, function(text) {
+          var json = eval("(" + text + ")");
+
+          /* Header */
+          var html = "<a href='#' onclick=\"setEnvPath('')\">env</a>";
+          var acc = "";
+          var p = json.path != "" ? json.path.split(".") : [];
+          for (var i = 0; i < p.length; i++) {
+            acc += "." + p[i];
+            html += " <a href='#' onclick=\"setEnvPath('" + acc + "')\">" +
+                    truncate(p[i], 10) + "</a>";
+          }
+          updateDivContent("envheader", html);
+
+          /* Handle invalid table path */
+          if (!json.valid) {
+            updateDivContent("envvars", "Bad path");
+            return;
+          }
+
+          /* Variables */
+          var html = "<table>";
+          for (var i = 0; json.vars[i]; i++) {
+            var x = json.vars[i];
+            var fullpath = (json.path + "." + x.key).replace(/^\./, "");
+            var k = truncate(x.key, 15);
+            if (x.type == "table") {
+              k = "<a href='#' onclick=\"setEnvPath('" + fullpath + "')\">" +
+                  k + "</a>";
+            }
+            var v = "<a href='#' onclick=\"insertVar('" +
+                    fullpath.replace(/\.(-?[0-9]+)/g, "[$1]") +
+                    "');\">" + x.value + "</a>"
+            html += "<tr><td>" + k + "</td><td>" + v + "</td></tr>";
+          }
+          html += "</table>";
+          updateDivContent("envvars", html);
+        });
+      }
+      var setEnvPath = function(p) {
+        envPath = p;
+        refreshEnv();
+      }
+      var insertVar = function(p) {
+        var b = document.getElementById("inputbox");
+        b.value += p;
+        b.focus();
+      }
+      setInterval(refreshEnv, <?lua echo(lovebird.updateinterval) ?> * 1000);
+    </script>
+  </body>
+</html>
+]]
+
+
+lovebird.pages["buffer"] = [[ <?lua echo(lovebird.buffer) ?> ]]
+
+
+lovebird.pages["env.json"] = [[
+<?lua
+  local t = _G
+  local p = req.parsedurl.query.p or ""
+  p = p:gsub("%.+", "."):match("^[%.]*(.*)[%.]*$")
+  if p ~= "" then
+    for x in p:gmatch("[^%.]+") do
+      t = t[x] or t[tonumber(x)]
+      -- Return early if path does not exist
+      if type(t) ~= "table" then
+        echo('{ "valid": false, "path": ' .. string.format("%q", p) .. ' }')
+        return
+      end
+    end
+  end
+?>
+{
+  "valid": true,
+  "path": "<?lua echo(p) ?>",
+  "vars": [
+    <?lua
+      local keys = {}
+      for k in pairs(t) do
+        if type(k) == "number" or type(k) == "string" then
+          table.insert(keys, k)
+        end
+      end
+      table.sort(keys, lovebird.compare)
+      for _, k in pairs(keys) do
+        local v = t[k]
+    ?>
+      {
+        "key": "<?lua echo(k) ?>",
+        "value": <?lua echo(
+                          string.format("%q",
+                            lovebird.truncate(
+                              lovebird.htmlescape(
+                                tostring(v)), 26))) ?>,
+        "type": "<?lua echo(type(v)) ?>",
+      },
+    <?lua end ?>
+  ]
+}
+]]
+
+
+
+function lovebird.init()
+  -- Init server
+  lovebird.server = assert(socket.bind(lovebird.host, lovebird.port))
+  lovebird.addr, lovebird.port = lovebird.server:getsockname()
+  lovebird.server:settimeout(0)
+  -- Wrap print
+  lovebird.origprint = print
+  if lovebird.wrapprint then
+    local oldprint = print
+    print = function(...)
+      oldprint(...)
+      lovebird.print(...)
+    end
+  end
+  -- Compile page templates
+  for k, page in pairs(lovebird.pages) do
+    lovebird.pages[k] = lovebird.template(page, "lovebird, req",
+                                          "pages." .. k)
+  end
+  lovebird.inited = true
+end
+
+
+function lovebird.template(str, params, chunkname)
+  params = params and ("," .. params) or ""
+  local f = function(x) return string.format(" echo(%q)", x) end
+  str = ("?>"..str.."<?lua"):gsub("%?>(.-)<%?lua", f)
+  str = "local echo " .. params .. " = ..." .. str
+  local fn = assert(lovebird.loadstring(str, chunkname))
+  return function(...)
+    local output = {}
+    local echo = function(str) table.insert(output, str) end
+    fn(echo, ...)
+    return table.concat(lovebird.map(output, tostring))
+  end
+end
+
+
+function lovebird.map(t, fn)
+  local res = {}
+  for k, v in pairs(t) do res[k] = fn(v) end
+  return res
+end
+
+
+function lovebird.trace(...)
+  local str = "[lovebird] " .. table.concat(lovebird.map({...}, tostring), " ")
+  print(str)
+  if not lovebird.wrapprint then lovebird.print(str) end
+end
+
+
+function lovebird.unescape(str)
+  local f = function(x) return string.char(tonumber("0x"..x)) end
+  return (str:gsub("%+", " "):gsub("%%(..)", f))
+end
+
+
+function lovebird.parseurl(url)
+  local res = {}
+  res.path, res.search = url:match("/([^%?]*)%??(.*)")
+  res.query = {}
+  for k, v in res.search:gmatch("([^&^?]-)=([^&^#]*)") do
+    res.query[k] = lovebird.unescape(v)
+  end
+  return res
+end
+
+
+local htmlescapemap = {
+  ["<"] = "&lt;",
+  ["&"] = "&amp;",
+  ['"'] = "&quot;",
+  ["'"] = "&#039;",
+}
+
+function lovebird.htmlescape(str)
+  return ( str:gsub("[<&\"']", htmlescapemap) )
+end
+
+
+function lovebird.truncate(str, len)
+  if #str <= len then
+    return str
+  end
+  return str:sub(1, len - 3) .. "..."
+end
+
+
+function lovebird.compare(a, b)
+  local na, nb = tonumber(a), tonumber(b)
+  if na then
+    if nb then return na < nb end
+    return false
+  elseif nb then
+    return true
+  end
+  return tostring(a) < tostring(b)
+end
+
+
+function lovebird.checkwhitelist(addr)
+  if lovebird.whitelist == nil then return true end
+  for _, a in pairs(lovebird.whitelist) do
+    local ptn = "^" .. a:gsub("%.", "%%."):gsub("%*", "%%d*") .. "$"
+    if addr:match(ptn) then return true end
+  end
+  return false
+end
+
+
+function lovebird.clear()
+  lovebird.lines = {}
+  lovebird.buffer = ""
+end
+
+
+function lovebird.pushline(line)
+  line.time = os.time()
+  line.count = 1
+  table.insert(lovebird.lines, line)
+  if #lovebird.lines > lovebird.maxlines then
+    table.remove(lovebird.lines, 1)
+  end
+  lovebird.recalcbuffer()
+end
+
+
+function lovebird.recalcbuffer()
+  local function doline(line)
+    local str = line.str
+    if not lovebird.allowhtml then
+      str = lovebird.htmlescape(line.str):gsub("\n", "<br>")
+    end
+    if line.type == "input" then
+      str = '<span class="inputline">' .. str .. '</span>'
+    else
+      if line.type == "error" then
+        str = '<span class="errormarker">!</span> ' .. str
+        str = '<span class="errorline">' .. str .. '</span>'
+      end
+      if line.count > 1 then
+        str = '<span class="repeatcount">' .. line.count .. '</span> ' .. str
+      end
+      if lovebird.timestamp then
+        str = os.date('<span class="timestamp">%H:%M:%S</span> ', line.time) ..
+              str
+      end
+    end
+    return str
+  end
+  lovebird.buffer = table.concat(lovebird.map(lovebird.lines, doline), "<br>")
+end
+
+
+function lovebird.print(...)
+  local t = {}
+  for i = 1, select("#", ...) do
+    table.insert(t, tostring(select(i, ...)))
+  end
+  local str = table.concat(t, " ")
+  local last = lovebird.lines[#lovebird.lines]
+  if last and str == last.str then
+    -- Update last line if this line is a duplicate of it
+    last.time = os.time()
+    last.count = last.count + 1
+    lovebird.recalcbuffer()
+  else
+    -- Create new line
+    lovebird.pushline({ type = "output", str = str })
+  end
+end
+
+
+function lovebird.onerror(err)
+  lovebird.pushline({ type = "error", str = err })
+  if lovebird.wrapprint then
+    lovebird.origprint("[lovebird] ERROR: " .. err)
+  end
+end
+
+
+function lovebird.onrequest(req, client)
+  local page = req.parsedurl.path
+  page = page ~= "" and page or "index"
+  -- Handle "page not found"
+  if not lovebird.pages[page] then
+    return "HTTP/1.1 404\r\nContent-Length: 8\r\n\r\nBad page"
+  end
+  -- Handle page
+  local str
+  xpcall(function()
+    local data = lovebird.pages[page](lovebird, req)
+    local contenttype = "text/html"
+    if string.match(page, "%.json$") then
+      contenttype = "application/json"
+    end
+    str = "HTTP/1.1 200 OK\r\n" ..
+          "Content-Type: " .. contenttype .. "\r\n" ..
+          "Content-Length: " .. #data .. "\r\n" ..
+          "\r\n" .. data
+  end, lovebird.onerror)
+  return str
+end
+
+
+function lovebird.receive(client, pattern)
+  while 1 do
+    local data, msg = client:receive(pattern)
+    if not data then
+      if msg == "timeout" then
+        -- Wait for more data
+        coroutine.yield(true)
+      else
+        -- Disconnected -- yielding nil means we're done
+        coroutine.yield(nil)
+      end
+    else
+      return data
+    end
+  end
+end
+
+
+function lovebird.send(client, data)
+  local idx = 1
+  while idx < #data do
+    local res, msg = client:send(data, idx)
+    if not res and msg == "closed" then
+      -- Handle disconnect
+      coroutine.yield(nil)
+    else
+      idx = idx + res
+      coroutine.yield(true)
+    end
+  end
+end
+
+
+function lovebird.onconnect(client)
+  -- Create request table
+  local requestptn = "(%S*)%s*(%S*)%s*(%S*)"
+  local req = {}
+  req.socket = client
+  req.addr, req.port = client:getsockname()
+  req.request = lovebird.receive(client, "*l")
+  req.method, req.url, req.proto = req.request:match(requestptn)
+  req.headers = {}
+  while 1 do
+    local line, msg = lovebird.receive(client, "*l")
+    if not line or #line == 0 then break end
+    local k, v = line:match("(.-):%s*(.*)$")
+    req.headers[k] = v
+  end
+  if req.headers["Content-Length"] then
+    req.body = lovebird.receive(client, req.headers["Content-Length"])
+  end
+  -- Parse body
+  req.parsedbody = {}
+  if req.body then
+    for k, v in req.body:gmatch("([^&]-)=([^&^#]*)") do
+      req.parsedbody[k] = lovebird.unescape(v)
+    end
+  end
+  -- Parse request line's url
+  req.parsedurl = lovebird.parseurl(req.url)
+  -- Handle request; get data to send and send
+  local data = lovebird.onrequest(req)
+  lovebird.send(client, data)
+  -- Clear up
+  client:close()
+end
+
+
+function lovebird.update()
+  if not lovebird.inited then lovebird.init() end
+  -- Handle new connections
+  while 1 do
+    -- Accept new connections
+    local client = lovebird.server:accept()
+    if not client then break end
+    client:settimeout(0)
+    local addr = client:getsockname()
+    if lovebird.checkwhitelist(addr) then
+      -- Connection okay -- create and add coroutine to set
+      local conn = coroutine.wrap(function()
+        xpcall(function() lovebird.onconnect(client) end, function() end)
+      end)
+      lovebird.connections[conn] = true
+    else
+      -- Reject connection not on whitelist
+      lovebird.trace("got non-whitelisted connection attempt: ", addr)
+      client:close()
+    end
+  end
+  -- Handle existing connections
+  for conn in pairs(lovebird.connections) do
+    -- Resume coroutine, remove if it has finished
+    local status = conn()
+    if status == nil then
+      lovebird.connections[conn] = nil
+    end
+  end
+end
+
+
+return lovebird
\ No newline at end of file
diff --git a/main/plugin/windfield/init.lua b/main/plugin/windfield/init.lua
new file mode 100644 (file)
index 0000000..8554822
--- /dev/null
@@ -0,0 +1,929 @@
+--[[\r
+The MIT License (MIT)\r
+\r
+Copyright (c) 2018 SSYGEN\r
+\r
+Permission is hereby granted, free of charge, to any person obtaining a copy\r
+of this software and associated documentation files (the "Software"), to deal\r
+in the Software without restriction, including without limitation the rights\r
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r
+copies of the Software, and to permit persons to whom the Software is\r
+furnished to do so, subject to the following conditions:\r
+\r
+The above copyright notice and this permission notice shall be included in all\r
+copies or substantial portions of the Software.\r
+\r
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\r
+SOFTWARE.\r
+]]--\r
+\r
+local path = ... .. '.' \r
+local wf = {} \r
+wf.Math = require(path .. 'mlib.mlib') \r
+\r
+World = {}\r
+World.__index = World \r
+\r
+function wf.newWorld(xg, yg, sleep)\r
+    local world = wf.World.new(wf, xg, yg, sleep)\r
+\r
+    world.box2d_world:setCallbacks(world.collisionOnEnter, world.collisionOnExit, world.collisionPre, world.collisionPost)\r
+    world:collisionClear()\r
+    world:addCollisionClass('Default')\r
+\r
+    -- Points all box2d_world functions to this wf.World object\r
+    -- This means that the user can call world:setGravity for instance without having to say world.box2d_world:setGravity\r
+    for k, v in pairs(world.box2d_world.__index) do \r
+        if k ~= '__gc' and k ~= '__eq' and k ~= '__index' and k ~= '__tostring' and k ~= 'update' and k ~= 'destroy' and k ~= 'type' and k ~= 'typeOf' then\r
+            world[k] = function(self, ...)\r
+                return v(self.box2d_world, ...)\r
+            end\r
+        end\r
+    end\r
+\r
+    return world\r
+end\r
+\r
+function World.new(wf, xg, yg, sleep)\r
+    local self = {}\r
+    local settings = settings or {}\r
+    self.wf = wf\r
+\r
+    self.draw_query_for_n_frames = 10\r
+    self.query_debug_drawing_enabled = false\r
+    self.explicit_collision_events = false\r
+    self.collision_classes = {}\r
+    self.masks = {}\r
+    self.is_sensor_memo = {}\r
+    self.query_debug_draw = {}\r
+\r
+    love.physics.setMeter(32)\r
+    self.box2d_world = love.physics.newWorld(xg, yg, sleep) \r
+\r
+    return setmetatable(self, World)\r
+end\r
+\r
+function World:update(dt)\r
+    self:collisionEventsClear()\r
+    self.box2d_world:update(dt)\r
+end\r
+\r
+function World:draw(alpha)\r
+    -- get the current color values to reapply\r
+    local r, g, b, a = love.graphics.getColor()\r
+    -- alpha value is optional\r
+    alpha = alpha or 255\r
+    -- Colliders debug\r
+    love.graphics.setColor(222, 222, 222, alpha)\r
+    local bodies = self.box2d_world:getBodies()\r
+    for _, body in ipairs(bodies) do\r
+        local fixtures = body:getFixtures()\r
+        for _, fixture in ipairs(fixtures) do\r
+            if fixture:getShape():type() == 'PolygonShape' then\r
+                love.graphics.polygon('line', body:getWorldPoints(fixture:getShape():getPoints()))\r
+            elseif fixture:getShape():type() == 'EdgeShape' or fixture:getShape():type() == 'ChainShape' then\r
+                local points = {body:getWorldPoints(fixture:getShape():getPoints())}\r
+                for i = 1, #points, 2 do\r
+                    if i < #points-2 then love.graphics.line(points[i], points[i+1], points[i+2], points[i+3]) end\r
+                end\r
+            elseif fixture:getShape():type() == 'CircleShape' then\r
+                local body_x, body_y = body:getPosition()\r
+                local shape_x, shape_y = fixture:getShape():getPoint()\r
+                local r = fixture:getShape():getRadius()\r
+                love.graphics.circle('line', body_x + shape_x, body_y + shape_y, r, 360)\r
+            end\r
+        end\r
+    end\r
+    love.graphics.setColor(255, 255, 255, alpha)\r
+\r
+    -- Joint debug\r
+    love.graphics.setColor(222, 128, 64, alpha)\r
+    local joints = self.box2d_world:getJoints()\r
+    for _, joint in ipairs(joints) do\r
+        local x1, y1, x2, y2 = joint:getAnchors()\r
+        if x1 and y1 then love.graphics.circle('line', x1, y1, 4) end\r
+        if x2 and y2 then love.graphics.circle('line', x2, y2, 4) end\r
+    end\r
+    love.graphics.setColor(255, 255, 255, alpha)\r
+\r
+    -- Query debug\r
+    love.graphics.setColor(64, 64, 222, alpha)\r
+    for _, query_draw in ipairs(self.query_debug_draw) do\r
+        query_draw.frames = query_draw.frames - 1\r
+        if query_draw.type == 'circle' then\r
+            love.graphics.circle('line', query_draw.x, query_draw.y, query_draw.r)\r
+        elseif query_draw.type == 'rectangle' then\r
+            love.graphics.rectangle('line', query_draw.x, query_draw.y, query_draw.w, query_draw.h)\r
+        elseif query_draw.type == 'line' then\r
+            love.graphics.line(query_draw.x1, query_draw.y1, query_draw.x2, query_draw.y2)\r
+        elseif query_draw.type == 'polygon' then\r
+            local triangles = love.math.triangulate(query_draw.vertices)\r
+            for _, triangle in ipairs(triangles) do love.graphics.polygon('line', triangle) end\r
+        end\r
+    end\r
+    for i = #self.query_debug_draw, 1, -1 do\r
+        if self.query_debug_draw[i].frames <= 0 then\r
+            table.remove(self.query_debug_draw, i)\r
+        end\r
+    end\r
+    love.graphics.setColor(r, g, b, a)\r
+end\r
+\r
+function World:setQueryDebugDrawing(value)\r
+    self.query_debug_drawing_enabled = value\r
+end\r
+\r
+function World:setExplicitCollisionEvents(value)\r
+    self.explicit_collision_events = value\r
+end\r
+\r
+function World:addCollisionClass(collision_class_name, collision_class)\r
+    if self.collision_classes[collision_class_name] then error('Collision class ' .. collision_class_name .. ' already exists.') end\r
+\r
+    if self.explicit_collision_events then\r
+        self.collision_classes[collision_class_name] = collision_class or {}\r
+    else\r
+        self.collision_classes[collision_class_name] = collision_class or {}\r
+        self.collision_classes[collision_class_name].enter = {}\r
+        self.collision_classes[collision_class_name].exit = {}\r
+        self.collision_classes[collision_class_name].pre = {}\r
+        self.collision_classes[collision_class_name].post = {}\r
+        for c_class_name, _ in pairs(self.collision_classes) do\r
+            table.insert(self.collision_classes[collision_class_name].enter, c_class_name)\r
+            table.insert(self.collision_classes[collision_class_name].exit, c_class_name)\r
+            table.insert(self.collision_classes[collision_class_name].pre, c_class_name)\r
+            table.insert(self.collision_classes[collision_class_name].post, c_class_name)\r
+        end\r
+        for c_class_name, _ in pairs(self.collision_classes) do\r
+            table.insert(self.collision_classes[c_class_name].enter, collision_class_name)\r
+            table.insert(self.collision_classes[c_class_name].exit, collision_class_name)\r
+            table.insert(self.collision_classes[c_class_name].pre, collision_class_name)\r
+            table.insert(self.collision_classes[c_class_name].post, collision_class_name)\r
+        end\r
+    end\r
+\r
+    self:collisionClassesSet()\r
+end\r
+\r
+function World:collisionClassesSet()\r
+    self:generateCategoriesMasks()\r
+\r
+    self:collisionClear()\r
+    local collision_table = self:getCollisionCallbacksTable()\r
+    for collision_class_name, collision_list in pairs(collision_table) do\r
+        for _, collision_info in ipairs(collision_list) do\r
+            if collision_info.type == 'enter' then self:addCollisionEnter(collision_class_name, collision_info.other) end\r
+            if collision_info.type == 'exit' then self:addCollisionExit(collision_class_name, collision_info.other) end\r
+            if collision_info.type == 'pre' then self:addCollisionPre(collision_class_name, collision_info.other) end\r
+            if collision_info.type == 'post' then self:addCollisionPost(collision_class_name, collision_info.other) end\r
+        end\r
+    end\r
+\r
+    self:collisionEventsClear()\r
+end\r
+\r
+function World:collisionClear()\r
+    self.collisions = {}\r
+    self.collisions.on_enter = {}\r
+    self.collisions.on_enter.sensor = {}\r
+    self.collisions.on_enter.non_sensor = {}\r
+    self.collisions.on_exit = {}\r
+    self.collisions.on_exit.sensor = {}\r
+    self.collisions.on_exit.non_sensor = {}\r
+    self.collisions.pre = {}\r
+    self.collisions.pre.sensor = {}\r
+    self.collisions.pre.non_sensor = {}\r
+    self.collisions.post = {}\r
+    self.collisions.post.sensor = {}\r
+    self.collisions.post.non_sensor = {}\r
+end\r
+\r
+function World:collisionEventsClear()\r
+    local bodies = self.box2d_world:getBodies()\r
+    for _, body in ipairs(bodies) do\r
+        local collider = body:getFixtures()[1]:getUserData()\r
+        collider:collisionEventsClear()\r
+    end\r
+end\r
+\r
+function World:addCollisionEnter(type1, type2)\r
+    if not self:isCollisionBetweenSensors(type1, type2) then\r
+        table.insert(self.collisions.on_enter.non_sensor, {type1 = type1, type2 = type2})\r
+    else table.insert(self.collisions.on_enter.sensor, {type1 = type1, type2 = type2}) end\r
+end\r
+\r
+function World:addCollisionExit(type1, type2)\r
+    if not self:isCollisionBetweenSensors(type1, type2) then\r
+        table.insert(self.collisions.on_exit.non_sensor, {type1 = type1, type2 = type2})\r
+    else table.insert(self.collisions.on_exit.sensor, {type1 = type1, type2 = type2}) end\r
+end\r
+\r
+function World:addCollisionPre(type1, type2)\r
+    if not self:isCollisionBetweenSensors(type1, type2) then\r
+        table.insert(self.collisions.pre.non_sensor, {type1 = type1, type2 = type2})\r
+    else table.insert(self.collisions.pre.sensor, {type1 = type1, type2 = type2}) end\r
+end\r
+\r
+function World:addCollisionPost(type1, type2)\r
+    if not self:isCollisionBetweenSensors(type1, type2) then\r
+        table.insert(self.collisions.post.non_sensor, {type1 = type1, type2 = type2})\r
+    else table.insert(self.collisions.post.sensor, {type1 = type1, type2 = type2}) end\r
+end\r
+\r
+function World:doesType1IgnoreType2(type1, type2)\r
+    local collision_ignores = {}\r
+    for collision_class_name, collision_class in pairs(self.collision_classes) do\r
+        collision_ignores[collision_class_name] = collision_class.ignores or {}\r
+    end\r
+    local all = {}\r
+    for collision_class_name, _ in pairs(collision_ignores) do\r
+        table.insert(all, collision_class_name)\r
+    end\r
+    local ignored_types = {}\r
+    for _, collision_class_type in ipairs(collision_ignores[type1]) do\r
+        if collision_class_type == 'All' then\r
+            for _, collision_class_name in ipairs(all) do\r
+                table.insert(ignored_types, collision_class_name)\r
+            end\r
+        else table.insert(ignored_types, collision_class_type) end\r
+    end\r
+    for key, _ in pairs(collision_ignores[type1]) do\r
+        if key == 'except' then\r
+            for _, except_type in ipairs(collision_ignores[type1].except) do\r
+                for i = #ignored_types, 1, -1 do\r
+                    if ignored_types[i] == except_type then table.remove(ignored_types, i) end\r
+                end\r
+            end\r
+        end\r
+    end\r
+    for _, ignored_type in ipairs(ignored_types) do\r
+        if ignored_type == type2 then return true end\r
+    end\r
+end\r
+\r
+function World:isCollisionBetweenSensors(type1, type2)\r
+    if not self.is_sensor_memo[type1] then self.is_sensor_memo[type1] = {} end\r
+    if not self.is_sensor_memo[type1][type2] then self.is_sensor_memo[type1][type2] = (self:doesType1IgnoreType2(type1, type2) or self:doesType1IgnoreType2(type2, type1)) end\r
+    if self.is_sensor_memo[type1][type2] then return true\r
+    else return false end\r
+end\r
+\r
+-- https://love2d.org/forums/viewtopic.php?f=4&t=75441\r
+function World:generateCategoriesMasks()\r
+    local collision_ignores = {}\r
+    for collision_class_name, collision_class in pairs(self.collision_classes) do\r
+        collision_ignores[collision_class_name] = collision_class.ignores or {}\r
+    end\r
+    local incoming = {}\r
+    local expanded = {}\r
+    local all = {}\r
+    for object_type, _ in pairs(collision_ignores) do\r
+        incoming[object_type] = {}\r
+        expanded[object_type] = {}\r
+        table.insert(all, object_type)\r
+    end\r
+    for object_type, ignore_list in pairs(collision_ignores) do\r
+        for key, ignored_type in pairs(ignore_list) do\r
+            if ignored_type == 'All' then\r
+                for _, all_object_type in ipairs(all) do\r
+                    table.insert(incoming[all_object_type], object_type)\r
+                    table.insert(expanded[object_type], all_object_type)\r
+                end\r
+            elseif type(ignored_type) == 'string' then\r
+                if ignored_type ~= 'All' then\r
+                    table.insert(incoming[ignored_type], object_type)\r
+                    table.insert(expanded[object_type], ignored_type)\r
+                end\r
+            end\r
+            if key == 'except' then\r
+                for _, except_ignored_type in ipairs(ignored_type) do\r
+                    for i, v in ipairs(incoming[except_ignored_type]) do\r
+                        if v == object_type then\r
+                            table.remove(incoming[except_ignored_type], i)\r
+                            break\r
+                        end\r
+                    end\r
+                end\r
+                for _, except_ignored_type in ipairs(ignored_type) do\r
+                    for i, v in ipairs(expanded[object_type]) do\r
+                        if v == except_ignored_type then\r
+                            table.remove(expanded[object_type], i)\r
+                            break\r
+                        end\r
+                    end\r
+                end\r
+            end\r
+        end\r
+    end\r
+    local edge_groups = {}\r
+    for k, v in pairs(incoming) do\r
+        table.sort(v, function(a, b) return string.lower(a) < string.lower(b) end)\r
+    end\r
+    local i = 0\r
+    for k, v in pairs(incoming) do\r
+        local str = ""\r
+        for _, c in ipairs(v) do\r
+            str = str .. c\r
+        end\r
+        if not edge_groups[str] then i = i + 1; edge_groups[str] = {n = i} end\r
+        table.insert(edge_groups[str], k)\r
+    end\r
+    local categories = {}\r
+    for k, _ in pairs(collision_ignores) do\r
+        categories[k] = {}\r
+    end\r
+    for k, v in pairs(edge_groups) do\r
+        for i, c in ipairs(v) do\r
+            categories[c] = v.n\r
+        end\r
+    end\r
+    for k, v in pairs(expanded) do\r
+        local category = {categories[k]}\r
+        local current_masks = {}\r
+        for _, c in ipairs(v) do\r
+            table.insert(current_masks, categories[c])\r
+        end\r
+        self.masks[k] = {categories = category, masks = current_masks}\r
+    end\r
+end\r
+\r
+function World:getCollisionCallbacksTable()\r
+    local collision_table = {}\r
+    for collision_class_name, collision_class in pairs(self.collision_classes) do\r
+        collision_table[collision_class_name] = {}\r
+        for _, v in ipairs(collision_class.enter or {}) do table.insert(collision_table[collision_class_name], {type = 'enter', other = v}) end\r
+        for _, v in ipairs(collision_class.exit or {}) do table.insert(collision_table[collision_class_name], {type = 'exit', other = v}) end\r
+        for _, v in ipairs(collision_class.pre or {}) do table.insert(collision_table[collision_class_name], {type = 'pre', other = v}) end\r
+        for _, v in ipairs(collision_class.post or {}) do table.insert(collision_table[collision_class_name], {type = 'post', other = v}) end\r
+    end\r
+    return collision_table\r
+end\r
+\r
+local function collEnsure(collision_class_name1, a, collision_class_name2, b)\r
+    if a.collision_class == collision_class_name2 and b.collision_class == collision_class_name1 then return b, a\r
+    else return a, b end\r
+end\r
+\r
+local function collIf(collision_class_name1, collision_class_name2, a, b)\r
+    if (a.collision_class == collision_class_name1 and b.collision_class == collision_class_name2) or\r
+       (a.collision_class == collision_class_name2 and b.collision_class == collision_class_name1) then\r
+       return true\r
+    else return false end\r
+end\r
+\r
+function World.collisionOnEnter(fixture_a, fixture_b, contact)\r
+    local a, b = fixture_a:getUserData(), fixture_b:getUserData()\r
+\r
+    if fixture_a:isSensor() and fixture_b:isSensor() then\r
+        if a and b then\r
+            for _, collision in ipairs(a.world.collisions.on_enter.sensor) do\r
+                if collIf(collision.type1, collision.type2, a, b) then\r
+                    a, b = collEnsure(collision.type1, a, collision.type2, b)\r
+                    table.insert(a.collision_events[collision.type2], {collision_type = 'enter', collider_1 = a, collider_2 = b, contact = contact})\r
+                    if collision.type1 == collision.type2 then \r
+                        table.insert(b.collision_events[collision.type1], {collision_type = 'enter', collider_1 = b, collider_2 = a, contact = contact})\r
+                    end\r
+                end\r
+            end\r
+        end\r
+\r
+    elseif not (fixture_a:isSensor() or fixture_b:isSensor()) then\r
+        if a and b then\r
+            for _, collision in ipairs(a.world.collisions.on_enter.non_sensor) do\r
+                if collIf(collision.type1, collision.type2, a, b) then\r
+                    a, b = collEnsure(collision.type1, a, collision.type2, b)\r
+                    table.insert(a.collision_events[collision.type2], {collision_type = 'enter', collider_1 = a, collider_2 = b, contact = contact})\r
+                    if collision.type1 == collision.type2 then \r
+                        table.insert(b.collision_events[collision.type1], {collision_type = 'enter', collider_1 = b, collider_2 = a, contact = contact})\r
+                    end\r
+                end\r
+            end\r
+        end\r
+    end\r
+end\r
+\r
+function World.collisionOnExit(fixture_a, fixture_b, contact)\r
+    local a, b = fixture_a:getUserData(), fixture_b:getUserData()\r
+\r
+    if fixture_a:isSensor() and fixture_b:isSensor() then\r
+        if a and b then\r
+            for _, collision in ipairs(a.world.collisions.on_exit.sensor) do\r
+                if collIf(collision.type1, collision.type2, a, b) then\r
+                    a, b = collEnsure(collision.type1, a, collision.type2, b)\r
+                    table.insert(a.collision_events[collision.type2], {collision_type = 'exit', collider_1 = a, collider_2 = b, contact = contact})\r
+                    if collision.type1 == collision.type2 then \r
+                        table.insert(b.collision_events[collision.type1], {collision_type = 'exit', collider_1 = b, collider_2 = a, contact = contact})\r
+                    end\r
+                end\r
+            end\r
+        end\r
+\r
+    elseif not (fixture_a:isSensor() or fixture_b:isSensor()) then\r
+        if a and b then\r
+            for _, collision in ipairs(a.world.collisions.on_exit.non_sensor) do\r
+                if collIf(collision.type1, collision.type2, a, b) then\r
+                    a, b = collEnsure(collision.type1, a, collision.type2, b)\r
+                    table.insert(a.collision_events[collision.type2], {collision_type = 'exit', collider_1 = a, collider_2 = b, contact = contact})\r
+                    if collision.type1 == collision.type2 then \r
+                        table.insert(b.collision_events[collision.type1], {collision_type = 'exit', collider_1 = b, collider_2 = a, contact = contact})\r
+                    end\r
+                end\r
+            end\r
+        end\r
+    end\r
+end\r
+\r
+function World.collisionPre(fixture_a, fixture_b, contact)\r
+    local a, b = fixture_a:getUserData(), fixture_b:getUserData()\r
+\r
+    if fixture_a:isSensor() and fixture_b:isSensor() then\r
+        if a and b then\r
+            for _, collision in ipairs(a.world.collisions.pre.sensor) do\r
+                if collIf(collision.type1, collision.type2, a, b) then\r
+                    a, b = collEnsure(collision.type1, a, collision.type2, b)\r
+                    a:preSolve(b, contact)\r
+                    if collision.type1 == collision.type2 then \r
+                        b:preSolve(a, contact)\r
+                    end\r
+                end\r
+            end\r
+        end\r
+\r
+    elseif not (fixture_a:isSensor() or fixture_b:isSensor()) then\r
+        if a and b then\r
+            for _, collision in ipairs(a.world.collisions.pre.non_sensor) do\r
+                if collIf(collision.type1, collision.type2, a, b) then\r
+                    a, b = collEnsure(collision.type1, a, collision.type2, b)\r
+                    a:preSolve(b, contact)\r
+                    if collision.type1 == collision.type2 then \r
+                        b:preSolve(a, contact)\r
+                    end\r
+                end\r
+            end\r
+        end\r
+    end\r
+end\r
+\r
+function World.collisionPost(fixture_a, fixture_b, contact, ni1, ti1, ni2, ti2)\r
+    local a, b = fixture_a:getUserData(), fixture_b:getUserData()\r
+\r
+    if fixture_a:isSensor() and fixture_b:isSensor() then\r
+        if a and b then\r
+            for _, collision in ipairs(a.world.collisions.post.sensor) do\r
+                if collIf(collision.type1, collision.type2, a, b) then\r
+                    a, b = collEnsure(collision.type1, a, collision.type2, b)\r
+                    a:postSolve(b, contact, ni1, ti1, ni2, ti2)\r
+                    if collision.type1 == collision.type2 then \r
+                        b:postSolve(a, contact, ni1, ti1, ni2, ti2)\r
+                    end\r
+                end\r
+            end\r
+        end\r
+\r
+    elseif not (fixture_a:isSensor() or fixture_b:isSensor()) then\r
+        if a and b then\r
+            for _, collision in ipairs(a.world.collisions.post.non_sensor) do\r
+                if collIf(collision.type1, collision.type2, a, b) then\r
+                    a, b = collEnsure(collision.type1, a, collision.type2, b)\r
+                    a:postSolve(b, contact, ni1, ti1, ni2, ti2)\r
+                    if collision.type1 == collision.type2 then \r
+                        b:postSolve(a, contact, ni1, ti1, ni2, ti2)\r
+                    end\r
+                end\r
+            end\r
+        end\r
+    end\r
+end\r
+\r
+function World:newCircleCollider(x, y, r, settings)\r
+    return self.wf.Collider.new(self, 'Circle', x, y, r, settings)\r
+end\r
+\r
+function World:newRectangleCollider(x, y, w, h, settings)\r
+    return self.wf.Collider.new(self, 'Rectangle', x, y, w, h, settings)\r
+end\r
+\r
+function World:newBSGRectangleCollider(x, y, w, h, corner_cut_size, settings)\r
+    return self.wf.Collider.new(self, 'BSGRectangle', x, y, w, h, corner_cut_size, settings)\r
+end\r
+\r
+function World:newPolygonCollider(vertices, settings)\r
+    return self.wf.Collider.new(self, 'Polygon', vertices, settings)\r
+end\r
+\r
+function World:newLineCollider(x1, y1, x2, y2, settings)\r
+    return self.wf.Collider.new(self, 'Line', x1, y1, x2, y2, settings)\r
+end\r
+\r
+function World:newChainCollider(vertices, loop, settings)\r
+    return self.wf.Collider.new(self, 'Chain', vertices, loop, settings)\r
+end\r
+\r
+-- Internal AABB box2d query used before going for more specific and precise computations.\r
+function World:_queryBoundingBox(x1, y1, x2, y2)\r
+    local colliders = {}\r
+    local callback = function(fixture)\r
+        if not fixture:isSensor() then table.insert(colliders, fixture:getUserData()) end\r
+        return true\r
+    end\r
+    self.box2d_world:queryBoundingBox(x1, y1, x2, y2, callback)\r
+    return colliders\r
+end\r
+\r
+function World:collisionClassInCollisionClassesList(collision_class, collision_classes)\r
+    if collision_classes[1] == 'All' then\r
+        local all_collision_classes = {}\r
+        for class, _ in pairs(self.collision_classes) do\r
+            table.insert(all_collision_classes, class)\r
+        end\r
+        if collision_classes.except then\r
+            for _, except in ipairs(collision_classes.except) do\r
+                for i, class in ipairs(all_collision_classes) do\r
+                    if class == except then \r
+                        table.remove(all_collision_classes, i)\r
+                        break\r
+                    end\r
+                end\r
+            end\r
+        end\r
+        for _, class in ipairs(all_collision_classes) do\r
+            if class == collision_class then return true end\r
+        end\r
+    else\r
+        for _, class in ipairs(collision_classes) do\r
+            if class == collision_class then return true end\r
+        end\r
+    end\r
+end\r
+\r
+function World:queryCircleArea(x, y, radius, collision_class_names)\r
+    if not collision_class_names then collision_class_names = {'All'} end\r
+    if self.query_debug_drawing_enabled then table.insert(self.query_debug_draw, {type = 'circle', x = x, y = y, r = radius, frames = self.draw_query_for_n_frames}) end\r
+    \r
+    local colliders = self:_queryBoundingBox(x-radius, y-radius, x+radius, y+radius) \r
+    local outs = {}\r
+    for _, collider in ipairs(colliders) do\r
+        if self:collisionClassInCollisionClassesList(collider.collision_class, collision_class_names) then\r
+            for _, fixture in ipairs(collider.body:getFixtures()) do\r
+                if self.wf.Math.polygon.getCircleIntersection(x, y, radius, {collider.body:getWorldPoints(fixture:getShape():getPoints())}) then\r
+                    table.insert(outs, collider)\r
+                    break\r
+                end\r
+            end\r
+        end\r
+    end\r
+    return outs\r
+end\r
+\r
+function World:queryRectangleArea(x, y, w, h, collision_class_names)\r
+    if not collision_class_names then collision_class_names = {'All'} end\r
+    if self.query_debug_drawing_enabled then table.insert(self.query_debug_draw, {type = 'rectangle', x = x, y = y, w = w, h = h, frames = self.draw_query_for_n_frames}) end\r
+\r
+    local colliders = self:_queryBoundingBox(x, y, x+w, y+h) \r
+    local outs = {}\r
+    for _, collider in ipairs(colliders) do\r
+        if self:collisionClassInCollisionClassesList(collider.collision_class, collision_class_names) then\r
+            for _, fixture in ipairs(collider.body:getFixtures()) do\r
+                if self.wf.Math.polygon.isPolygonInside({x, y, x+w, y, x+w, y+h, x, y+h}, {collider.body:getWorldPoints(fixture:getShape():getPoints())}) then\r
+                    table.insert(outs, collider)\r
+                    break\r
+                end\r
+            end\r
+        end\r
+    end\r
+    return outs\r
+end\r
+\r
+function World:queryPolygonArea(vertices, collision_class_names)\r
+    if not collision_class_names then collision_class_names = {'All'} end\r
+    if self.query_debug_drawing_enabled then table.insert(self.query_debug_draw, {type = 'polygon', vertices = vertices, frames = self.draw_query_for_n_frames}) end\r
+\r
+    local cx, cy = self.wf.Math.polygon.getCentroid(vertices)\r
+    local d_max = 0\r
+    for i = 1, #vertices, 2 do\r
+        local d = self.wf.Math.line.getLength(cx, cy, vertices[i], vertices[i+1])\r
+        if d > d_max then d_max = d end\r
+    end\r
+    local colliders = self:_queryBoundingBox(cx-d_max, cy-d_max, cx+d_max, cy+d_max)\r
+    local outs = {}\r
+    for _, collider in ipairs(colliders) do\r
+        if self:collisionClassInCollisionClassesList(collider.collision_class, collision_class_names) then\r
+            for _, fixture in ipairs(collider.body:getFixtures()) do\r
+                if self.wf.Math.polygon.isPolygonInside(vertices, {collider.body:getWorldPoints(fixture:getShape():getPoints())}) then\r
+                    table.insert(outs, collider)\r
+                    break\r
+                end\r
+            end\r
+        end\r
+    end\r
+    return outs\r
+end\r
+\r
+function World:queryLine(x1, y1, x2, y2, collision_class_names)\r
+    if not collision_class_names then collision_class_names = {'All'} end\r
+    if self.query_debug_drawing_enabled then \r
+        table.insert(self.query_debug_draw, {type = 'line', x1 = x1, y1 = y1, x2 = x2, y2 = y2, frames = self.draw_query_for_n_frames}) \r
+    end\r
+\r
+    local colliders = {}\r
+    local callback = function(fixture, ...)\r
+        if not fixture:isSensor() then table.insert(colliders, fixture:getUserData()) end\r
+        return 1\r
+    end\r
+    self.box2d_world:rayCast(x1, y1, x2, y2, callback)\r
+\r
+    local outs = {}\r
+    for _, collider in ipairs(colliders) do\r
+        if self:collisionClassInCollisionClassesList(collider.collision_class, collision_class_names) then\r
+            table.insert(outs, collider)\r
+        end\r
+    end\r
+    return outs\r
+end\r
+\r
+function World:addJoint(joint_type, ...)\r
+    local args = {...}\r
+    if args[1].body then args[1] = args[1].body end\r
+    if type(args[2]) == "table" and args[2].body then args[2] = args[2].body end\r
+    local joint = love.physics['new' .. joint_type](unpack(args))\r
+    return joint\r
+end\r
+\r
+function World:removeJoint(joint)\r
+    joint:destroy()\r
+end\r
+\r
+function World:destroy()\r
+    local bodies = self.box2d_world:getBodies()\r
+    for _, body in ipairs(bodies) do\r
+        local collider = body:getFixtures()[1]:getUserData()\r
+        collider:destroy()\r
+    end\r
+    local joints = self.box2d_world:getJoints()\r
+    for _, joint in ipairs(joints) do joint:destroy() end\r
+    self.box2d_world:destroy()\r
+    self.box2d_world = nil\r
+end\r
+\r
+\r
+\r
+local Collider = {}\r
+Collider.__index = Collider\r
+\r
+local generator = love.math.newRandomGenerator(os.time())\r
+local function UUID()\r
+    local fn = function(x)\r
+        local r = generator:random(16) - 1\r
+        r = (x == "x") and (r + 1) or (r % 4) + 9\r
+        return ("0123456789abcdef"):sub(r, r)\r
+    end\r
+    return (("xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx"):gsub("[xy]", fn))\r
+end\r
+\r
+function Collider.new(world, collider_type, ...)\r
+    local self = {}\r
+    self.id = UUID()\r
+    self.world = world\r
+    self.type = collider_type\r
+    self.object = nil\r
+\r
+    self.shapes = {}\r
+    self.fixtures = {}\r
+    self.sensors = {}\r
+\r
+    self.collision_events = {}\r
+    self.collision_stay = {}\r
+    self.enter_collision_data = {}\r
+    self.exit_collision_data = {}\r
+    self.stay_collision_data = {}\r
+\r
+    local args = {...}\r
+    local shape, fixture\r
+    if self.type == 'Circle' then\r
+        self.collision_class = (args[4] and args[4].collision_class) or 'Default'\r
+        self.body = love.physics.newBody(self.world.box2d_world, args[1], args[2], (args[4] and args[4].body_type) or 'dynamic')\r
+        shape = love.physics.newCircleShape(args[3])\r
+\r
+    elseif self.type == 'Rectangle' then\r
+        self.collision_class = (args[5] and args[5].collision_class) or 'Default'\r
+        self.body = love.physics.newBody(self.world.box2d_world, args[1] + args[3]/2, args[2] + args[4]/2, (args[5] and args[5].body_type) or 'dynamic')\r
+        shape = love.physics.newRectangleShape(args[3], args[4])\r
+\r
+    elseif self.type == 'BSGRectangle' then\r
+        self.collision_class = (args[6] and args[6].collision_class) or 'Default'\r
+        self.body = love.physics.newBody(self.world.box2d_world, args[1] + args[3]/2, args[2] + args[4]/2, (args[6] and args[6].body_type) or 'dynamic')\r
+        local w, h, s = args[3], args[4], args[5]\r
+        shape = love.physics.newPolygonShape({\r
+            -w/2, -h/2 + s, -w/2 + s, -h/2,\r
+             w/2 - s, -h/2, w/2, -h/2 + s,\r
+             w/2, h/2 - s, w/2 - s, h/2,\r
+            -w/2 + s, h/2, -w/2, h/2 - s\r
+        })\r
+\r
+    elseif self.type == 'Polygon' then\r
+        self.collision_class = (args[2] and args[2].collision_class) or 'Default'\r
+        self.body = love.physics.newBody(self.world.box2d_world, 0, 0, (args[2] and args[2].body_type) or 'dynamic')\r
+        shape = love.physics.newPolygonShape(unpack(args[1]))\r
+\r
+    elseif self.type == 'Line' then\r
+        self.collision_class = (args[5] and args[5].collision_class) or 'Default'\r
+        self.body = love.physics.newBody(self.world.box2d_world, 0, 0, (args[5] and args[5].body_type) or 'dynamic')\r
+        shape = love.physics.newEdgeShape(args[1], args[2], args[3], args[4])\r
+\r
+    elseif self.type == 'Chain' then\r
+        self.collision_class = (args[3] and args[3].collision_class) or 'Default'\r
+        self.body = love.physics.newBody(self.world.box2d_world, 0, 0, (args[3] and args[3].body_type) or 'dynamic')\r
+        shape = love.physics.newChainShape(args[1], unpack(args[2]))\r
+    end\r
+\r
+    -- Define collision classes and attach them to fixture and sensor\r
+    fixture = love.physics.newFixture(self.body, shape)\r
+    if self.world.masks[self.collision_class] then\r
+        fixture:setCategory(unpack(self.world.masks[self.collision_class].categories))\r
+        fixture:setMask(unpack(self.world.masks[self.collision_class].masks))\r
+    end\r
+    fixture:setUserData(self)\r
+    local sensor = love.physics.newFixture(self.body, shape)\r
+    sensor:setSensor(true)\r
+    sensor:setUserData(self)\r
+\r
+    self.shapes['main'] = shape\r
+    self.fixtures['main'] = fixture\r
+    self.sensors['main'] = sensor\r
+    self.shape = shape\r
+    self.fixture = fixture\r
+    \r
+    self.preSolve = function() end\r
+    self.postSolve = function() end\r
+\r
+    -- Points all body, fixture and shape functions to this wf.Collider object\r
+    -- This means that the user can call collider:setLinearVelocity for instance without having to say collider.body:setLinearVelocity\r
+    for k, v in pairs(self.body.__index) do \r
+        if k ~= '__gc' and k ~= '__eq' and k ~= '__index' and k ~= '__tostring' and k ~= 'destroy' and k ~= 'type' and k ~= 'typeOf' then\r
+            self[k] = function(self, ...)\r
+                return v(self.body, ...)\r
+            end\r
+        end\r
+    end\r
+    for k, v in pairs(self.fixture.__index) do \r
+        if k ~= '__gc' and k ~= '__eq' and k ~= '__index' and k ~= '__tostring' and k ~= 'destroy' and k ~= 'type' and k ~= 'typeOf' then\r
+            self[k] = function(self, ...)\r
+                return v(self.fixture, ...)\r
+            end\r
+        end\r
+    end\r
+    for k, v in pairs(self.shape.__index) do \r
+        if k ~= '__gc' and k ~= '__eq' and k ~= '__index' and k ~= '__tostring' and k ~= 'destroy' and k ~= 'type' and k ~= 'typeOf' then\r
+            self[k] = function(self, ...)\r
+                return v(self.shape, ...)\r
+            end\r
+        end\r
+    end\r
+\r
+    return setmetatable(self, Collider)\r
+end\r
+\r
+function Collider:collisionEventsClear()\r
+    self.collision_events = {}\r
+    for other, _ in pairs(self.world.collision_classes) do\r
+        self.collision_events[other] = {}\r
+    end\r
+end\r
+\r
+function Collider:setCollisionClass(collision_class_name)\r
+    if not self.world.collision_classes[collision_class_name] then error("Collision class " .. collision_class_name .. " doesn't exist.") end\r
+    self.collision_class = collision_class_name\r
+    for _, fixture in pairs(self.fixtures) do\r
+        if self.world.masks[collision_class_name] then\r
+            fixture:setCategory(unpack(self.world.masks[collision_class_name].categories))\r
+            fixture:setMask(unpack(self.world.masks[collision_class_name].masks))\r
+        end\r
+    end\r
+end\r
+\r
+function Collider:enter(other_collision_class_name)\r
+    local events = self.collision_events[other_collision_class_name]\r
+    if events and #events >= 1  then\r
+        for _, e in ipairs(events) do\r
+            if e.collision_type == 'enter' then\r
+                if not self.collision_stay[other_collision_class_name] then self.collision_stay[other_collision_class_name] = {} end\r
+                table.insert(self.collision_stay[other_collision_class_name], {collider = e.collider_2, contact = e.contact})\r
+                self.enter_collision_data[other_collision_class_name] = {collider = e.collider_2, contact = e.contact}\r
+                return true\r
+            end\r
+        end\r
+    end\r
+end\r
+\r
+function Collider:getEnterCollisionData(other_collision_class_name)\r
+    return self.enter_collision_data[other_collision_class_name]\r
+end\r
+\r
+function Collider:exit(other_collision_class_name)\r
+    local events = self.collision_events[other_collision_class_name]\r
+    if events and #events >= 1  then\r
+        for _, e in ipairs(events) do\r
+            if e.collision_type == 'exit' then\r
+                if self.collision_stay[other_collision_class_name] then\r
+                    for i = #self.collision_stay[other_collision_class_name], 1, -1 do\r
+                        local collision_stay = self.collision_stay[other_collision_class_name][i]\r
+                        if collision_stay.collider.id == e.collider_2.id then table.remove(self.collision_stay[other_collision_class_name], i) end\r
+                    end\r
+                end\r
+                self.exit_collision_data[other_collision_class_name] = {collider = e.collider_2, contact = e.contact}\r
+                return true \r
+            end\r
+        end\r
+    end\r
+end\r
+\r
+function Collider:getExitCollisionData(other_collision_class_name)\r
+    return self.exit_collision_data[other_collision_class_name]\r
+end\r
+\r
+function Collider:stay(other_collision_class_name)\r
+    if self.collision_stay[other_collision_class_name] then\r
+        if #self.collision_stay[other_collision_class_name] >= 1 then\r
+            return true\r
+        end\r
+    end\r
+end\r
+\r
+function Collider:getStayCollisionData(other_collision_class_name)\r
+    return self.collision_stay[other_collision_class_name]\r
+end\r
+\r
+function Collider:setPreSolve(callback)\r
+    self.preSolve = callback\r
+end\r
+\r
+function Collider:setPostSolve(callback)\r
+    self.postSolve = callback\r
+end\r
+\r
+function Collider:setObject(object)\r
+    self.object = object \r
+end\r
+\r
+function Collider:getObject()\r
+    return self.object\r
+end\r
+\r
+function Collider:addShape(shape_name, shape_type, ...)\r
+    if self.shapes[shape_name] or self.fixtures[shape_name] then error("Shape/fixture " .. shape_name .. " already exists.") end\r
+    local args = {...}\r
+    local shape = love.physics['new' .. shape_type](unpack(args))\r
+    local fixture = love.physics.newFixture(self.body, shape)\r
+    if self.world.masks[self.collision_class] then\r
+        fixture:setCategory(unpack(self.world.masks[self.collision_class].categories))\r
+        fixture:setMask(unpack(self.world.masks[self.collision_class].masks))\r
+    end\r
+    fixture:setUserData(self)\r
+    local sensor = love.physics.newFixture(self.body, shape)\r
+    sensor:setSensor(true)\r
+    sensor:setUserData(self)\r
+\r
+    self.shapes[shape_name] = shape\r
+    self.fixtures[shape_name] = fixture\r
+    self.sensors[shape_name] = sensor\r
+end\r
+\r
+function Collider:removeShape(shape_name)\r
+    if not self.shapes[shape_name] then return end\r
+    self.shapes[shape_name] = nil\r
+    self.fixtures[shape_name]:setUserData(nil)\r
+    self.fixtures[shape_name]:destroy()\r
+    self.fixtures[shape_name] = nil\r
+    self.sensors[shape_name]:setUserData(nil)\r
+    self.sensors[shape_name]:destroy()\r
+    self.sensors[shape_name] = nil\r
+end\r
+\r
+function Collider:destroy()\r
+    self.collision_stay = nil\r
+    self.enter_collision_data = nil\r
+    self.exit_collision_data = nil\r
+    self:collisionEventsClear()\r
+\r
+    self:setObject(nil)\r
+    for name, _ in pairs(self.fixtures) do\r
+        self.shapes[name] = nil\r
+        self.fixtures[name]:setUserData(nil)\r
+        self.fixtures[name] = nil\r
+        self.sensors[name]:setUserData(nil)\r
+        self.sensors[name] = nil\r
+    end\r
+    self.body:destroy()\r
+    self.body = nil\r
+end\r
+\r
+wf.World = World\r
+wf.Collider = Collider\r
+\r
+return wf\r
+\r
diff --git a/main/plugin/windfield/mlib/Changes.txt b/main/plugin/windfield/mlib/Changes.txt
new file mode 100644 (file)
index 0000000..1b41d50
--- /dev/null
@@ -0,0 +1,568 @@
+0.11.0\r
+====\r
+Added:\r
+----\r
+- mlib.vec2 component\r
+\r
+To-Do: \r
+----\r
+- Update README.md\r
+- Update spec.lua\r
+- Fix tabbing\r
+\r
+0.10.1\r
+====\r
+Added:\r
+----\r
+- Point category\r
+       - point.rotate\r
+       - point.scale\r
+       - point.polarToCartesian\r
+       - point.cartesianToPolar\r
+\r
+Changed:\r
+----\r
+- math.getPercent now returns decimals (instead of percentages) since those are more common to use.\r
+\r
+To-Do:\r
+----\r
+- Determine if isCompletelyInsideFunctions should return true with tangents.\r
+- Check argument order for logicality and consistency.\r
+- Add error checking.\r
+- Make sure to see if any aliases were missed. (e.g. isSegmentInside)\r
+- Clean up and correct README (add "Home" link, etc.)\r
+\r
+0.10.0\r
+====\r
+Added:\r
+----\r
+\r
+Changed:\r
+----\r
+- mlib.line.segment is now mlib.segment.\r
+- mlib.line.getIntercept has been renamed to mlib.line.getYIntercept\r
+- mlib.line.getYIntercept now returns the x-coordinate for vertical lines instead of false.\r
+- mlib.line.getYIntercept now returns the value `isVertical` as the second return value.\r
+- mlib.line.getPerpendicularBisector is now mlib.segment.getPerpendicularBisector.\r
+\r
+Fixed:\r
+----\r
+- mlib.line.getIntersection now should handle vertical slopes better.\r
+- mlib.line.getClosestPoint now uses local function checkFuzzy for checking horizontal lines.\r
+- Fixed possible bug in mlib.line.getSegmentIntersection and vertical lines.\r
+- mlib.segment.getIntersection now uses fuzzy checking for parallel lines.\r
+- mlib.math.round is now much more efficient.\r
+- Removed some useless code from mlib.polygon.isSegmentInside.\r
+\r
+To-Do:\r
+----\r
+- Determine if isCompletelyInsideFunctions should return true with tangents.\r
+- Check argument order for logicality and consistency.\r
+- Improve speed.\r
+- Add error checking.\r
+- Make sure to see if any aliases were missed. (e.g. isSegmentInside)\r
+- Implement mlib.shapes again(?)\r
+- Clean up and correct README (add "Home" link, etc.)\r
+\r
+0.9.4\r
+====\r
+Added:\r
+----\r
+\r
+Changed:\r
+----\r
+- mlib.line.getDistance is now slightly faster.\r
+- Made code much easier to debug by using new utility `cycle`.\r
+- Added new utility.\r
+- Various other minor changes.\r
+\r
+Removed:\r
+----\r
+- Unused local utility function copy\r
+\r
+To-Do\r
+----\r
+- Determine if isCompletelyInsideFunctions should return true with tangents.\r
+- Make argument order more logical.\r
+- Improve speed and error checking.\r
+- Make sure to see if any aliases were missed. (e.g. isSegmentInside)\r
+- Implement mlib.shapes again(?)\r
+- Clean up README (add "Home" link, etc.)\r
+\r
+0.9.3\r
+====\r
+Added:\r
+----\r
+- milb.circle.isCircleCompletelyInside\r
+- mlib.circle.isPolygonCompletelyInside\r
+- milb.circle.isSegmentCompletelyInside\r
+- mlib.polygon.isCircleCompletelyInside\r
+- mlib.polygon.isPolygonCompletelyInside\r
+- mlib.polygon.isSegmentCompletelyInside\r
+\r
+               - ALIASES -\r
+- mlib.circle.getPolygonIntersection\r
+- mlib.circle.isCircleInsidePolygon\r
+- mlib.circle.isCircleCompletelyInsidePolygon\r
+- milb.line.getCircleIntersection\r
+- milb.line.getPolygonIntersection\r
+- milb.line.getLineIntersection\r
+- mlib.line.segment.getCircleIntersection\r
+- mlib.line.segment.getPolygonIntersection\r
+- mlib.line.segment.getLineIntersection\r
+- mlib.line.segment.getSegmentIntersection\r
+- mlib.line.segment.isSegmentCompletelyInsideCircle\r
+- mlib.line.segment.isSegmentCompletelyInsidePolygon\r
+- mlib.polygon.isCircleCompletelyOver\r
+\r
+Changed:\r
+----\r
+- mlib.circle.getCircleIntersection now returns 'inside' instead of 'intersection' if the point has not intersections but is within the circle.\r
+- Fixed problem involving mlib.circle.getSegmentIntersection\r
+\r
+- README.md now has more information on how to run specs and other minor improvements.\r
+- Fixed some commenting on explanation of derivation of mlib.line.getIntersection.\r
+- Updated the example to use the current version of mlib.\r
+- Made/Changed some comments in the example main.lua.\r
+\r
+Removed:\r
+----\r
+\r
+To-Do\r
+----\r
+- Make examples file on github (examples/shapes/main.lua, etc.) not just one line.\r
+- Determine if isCompletelyInsideFunctions should return true with tangents.\r
+- Make argument order more logical.\r
+- Make sure to see if any aliases were missed. (e.g. isSegmentInside)\r
+- Update spec links in README\r
+\r
+0.9.2\r
+====\r
+Added:\r
+----\r
+\r
+Changed:\r
+----\r
+- mlib.polygon.getPolygonIntersection now does not create duplicate local table.\r
+- mlib.line.getPerpendicularSlope now does not create a global variable.\r
+- mlib.math.getSummation now allows the error to go through instead of returning false if the stop value is not a number.\r
+\r
+- Changed any instance of the term "userdata" with "input"\r
+\r
+Removed:\r
+----\r
+\r
+0.9.1\r
+====\r
+Added:\r
+----\r
+- Added mlib.statistics.getCentralTendency\r
+- Added mlib.statistics.getDispersion\r
+- Added mlib.statistics.getStandardDeviation\r
+- Added mlib.statistics.getVariation\r
+- Added mlib.statistics.getVariationRatio\r
+\r
+Removed:\r
+----\r
+\r
+Changed:\r
+----\r
+- FIX:         mlib.polygon.checkPoint now handles vertices better.\r
+\r
+\r
+To-Do\r
+----\r
+- Add more functions.\r
+\r
+0.9.0\r
+====\r
+Added:\r
+----\r
+- mlib.line.getDistance as an alias for mlib.line.getLength.\r
+- mlib.line.checkPoint\r
+- Internal documentation.\r
+\r
+Removed:\r
+----\r
+- mlib.circle.isPointInCircle is replaced with mlib.circle.checkPoint\r
+- mlib.circle.checkPoint is replaced with mlib.circle.isPointOnCircle\r
+- Variation of mlib.circle.getLineIntersection( cx, cy, radius, slope, intercept ) is no longer supported, as it can cause errors with vertical lines.\r
+\r
+Changed:\r
+----\r
+- CHANGE:      mlib.line.getIntersection now returns true for colinear lines.\r
+- CHANGE:      mlib.line.getIntersection now returns true if the line are collinear.\r
+- CHANGE:      mlib.line.getIntersection now returns true if vertical lines are collinear.\r
+- CHANGE:      mlib.line.getSegmentIntersection now returns true if the line and segment are collinear.\r
+- CHANGE:      Changed the order of mlib.line.segment.checkPoint arguments.\r
+- NAME:        mlib.polygon.lineIntersects is now mlib.polygon.getLineIntersection\r
+- NAME:        mlib.polygon.lineSegmentIntersects is now mlib.polygon.getSegmentIntersection\r
+- NAME:        mlib.polygon.isLineSegmentInside is now mlib.polygon.isSegmentInside\r
+- NAME:        mlib.polygon.polygonIntersects is now mlib.polygon.getPolygonIntersection\r
+- CHANGED:     mlib.circle.checkPoint now takes arguments ( px, py, cx, cy, radius ).\r
+- CHANGED:     mlib.circle.isPointOnCircle now takes arguments ( px, py, cx, cy, radius ).\r
+- NAME:        mlib.polygon.circleIntersects is now mlib.polygon.getCircleIntersection\r
+- NAME:        mlib.circle.isLineSecant is now mlib.circle.getLineIntersection\r
+- NAME:        mlib.circle.isSegmentSecant is now mlib.circle.getSegmentIntersection\r
+- NAME:        mlib.circle.circlesIntersects is now mlib.circle.getCircleIntersection\r
+- CHANGE:      Added types 'tangent' and 'intersection' to mlib.circle.getCircleIntersection.\r
+- NAME:                mlib.math.getRootsOfQuadratic is now mlib.math.getQuadraticRoots\r
+- CHANGE:      mlib.math.getRoot now only returns the positive, since it there is not always negatives.\r
+- NAME:        mlib.math.getPercent is now mlib.math.getPercentage\r
+\r
+- Cleaned up code (added comments, spaced lines, etc.)\r
+- Made syntax that uses camelCase instead of CamelCase.\r
+       - Match style of more programmers.\r
+       - Easier to type.\r
+- Moved to semantic numbering.\r
+- Made any returns strings lower-case.\r
+- Updated specs for missing functions.\r
+\r
+To-Do\r
+----\r
+- Update readme.\r
+- Add mlib.statistics.getStandardDeviation\r
+- Add mlib.statistics.getMeasuresOfCentralTendency\r
+- Add mlib.statistics.getMeasuresOfDispersion\r
+\r
+1.1.0.2\r
+====\r
+Added:\r
+----\r
+- MLib.Polygon.IsPolygonInside\r
+\r
+Removed:\r
+----\r
+- Removed all MLib.Shape:\r
+       - Was very slow.\r
+       - Could not define custom callbacks.\r
+       - Allow for flexibility.\r
+\r
+Changed:\r
+----\r
+- Switched MLib.Line.GetIntersection back to the old way\r
+- MLib.Line.GetSegmentIntersection now returns 4 values if the lines are parallel.\r
+\r
+TODO:\r
+- Make it so that MLib.Shape objects can use ':' syntax for other functions (i.e. MLib.Line.GetLength for Line objects, etc.)\r
+- Intuitive error messages.\r
+\r
+\r
+1.1.0.1\r
+====\r
+Added:\r
+----\r
+\r
+Removed:\r
+----\r
+\r
+Changed:\r
+- MLib.Line.GetIntersection now returns true, instead of two points.\r
+\r
+----\r
+\r
+Fixed:\r
+----\r
+- MLib.Line.GetIntersection now handles vertical lines: returns true if they collide, false otherwise.\r
+- MLib.Polygon.LineIntersects now also handles verticals.\r
+\r
+TODO:\r
+- Fix\r
+       - MLib.Shape Table can't have metatables.\r
+\r
+1.1.0.0\r
+====\r
+Added:\r
+----\r
+- MLib.Polygon.IsCircleInside\r
+- MLib.Polygon.LineSegmentIntersects\r
+- MLib.Polygon.IsLineSegmentInside\r
+- MLib.Statistics.GetFrequency\r
+- MLib.Math.Factorial\r
+- MLib.Math.SystemOfEquations\r
+\r
+Removed:\r
+----\r
+\r
+Changed:\r
+----\r
+- MLib.Polygon.LineIntersects is now MLib.Polygon.LineSegmentIntersects.\r
+- Put Word-wrap on Changes.txt\r
+\r
+Fixed:\r
+----\r
+- Problems with numberous MLib.Polygon and MLib.Circle problems.\r
+\r
+TODO:\r
+- Fix\r
+       - MLib.Shape Table can't have metatables.\r
+\r
+1.0.0.3\r
+====\r
+Added:\r
+----\r
+\r
+Removed:\r
+----\r
+\r
+Changed:\r
+----\r
+\r
+Fixed:\r
+----\r
+- README.md\r
+\r
+TODO:\r
+- Add:\r
+  - Frequency\r
+  - Binomial Probability\r
+  - Standard Deviation\r
+  - Conditional Probability\r
+\r
+1.0.0.2\r
+====\r
+Added:\r
+----\r
+\r
+Removed:\r
+----\r
+- Ability to use a direction for Math.GetAngle's 5th argument instead of having a third point. See Fixed for more.\r
+\r
+Changed:\r
+----\r
+- Changed README.md for clarity and consistency.\r
+- Updated spec.lua\r
+- See Fixed for more.\r
+\r
+Fixed:\r
+----\r
+- Circle.IsSegmentSecant now properly accounts for chords actually being chords, and not secants.\r
+- Circle.CircleIntersects now can return 'Colinear' or 'Equal' if the circles have same x and y but different radii (Colinear) or are exactly the same (Equal).\r
+- Statistics.GetMode now returns a table with the modes, and the second argument as the number of times they appear.\r
+- Math.GetRoot now returns the negative number as a second argument.\r
+- Math.GetPercentOfChange now works for 0 to 0 (previously false).\r
+- Math.GetAngle now takes only three points and no direction option.\r
+- Typos in Shape.CheckCollisions and Shape.Remove.\r
+- Fixed nil problems in Shape.CheckCollisions.\r
+- Improved readablility and DRYness of Shape.CheckCollisions.\r
+- Bugs in Shape.Remove and Shape.CheckCollisions regarding passing tables as arguments.\r
+\r
+TODO:\r
+- Add:\r
+  - Frequency\r
+  - Binomial Probability\r
+  - Standard Deviation\r
+  - Conditional Probability\r
+\r
+1.0.0.1\r
+====\r
+Added:\r
+----\r
+\r
+Removed:\r
+----\r
+\r
+Changed:\r
+----\r
+- Changes.txt now expanded to include short excertps from all previous commits.\r
+- Changed release number from 3.0.0 to 1.0.0.1\r
+- Math.Round now can round to decimal places as the second argument.\r
+- Commented unnecessary call of Segment.CheckPoint in Polygon.LineIntersects.\r
+- Polygon.LineIntersects now returns where the lines intersect.\r
+       - false if not intersection.\r
+       - A table with all of the intersections { { px, py } }\r
+- Same with Polygon.PolygonIntersects, Polygon.CircleIntersects,\r
+\r
+Fixed:\r
+----\r
+- Error with GetSlope being called incorrectly.\r
+- README.md Line.GetPerpendicularSlope misdirection.\r
+- Same with Line.GetPerpendicularBisector, Line.Segment.GetIntersection, Circle.IsLineSecant, Circle.IsSegmentSecant, Statistics.GetMean, Median, Mode, and Range, and Shape:Remove, and fixed the naming for Shape:CheckCollisions and Shape:Remove.\r
+- Clarified README.md\r
+- Made util SortWithReferences local.\r
+- Errors caused by local functions.\r
+\r
+TODO:\r
+- Add:\r
+  - Frequency\r
+  - Binomial Probability\r
+  - Standard Deviation\r
+  - Conditional Probability\r
+\r
+3.0.0\r
+-----\r
+ADDED:\r
+- Added function GetSignedArea.\r
+REMOVED:\r
+- Removed drawing functions.\r
+- Removed MLib.Line.Functions entirely.\r
+CHANGED:\r
+- Changed all the names to CamelCase.\r
+- Changed module name to MLib.\r
+- Changed return order of GetPerpendicualrBisector from Slope, Midpoint to Midpoint, Slope.\r
+- Changed returned string of MLib.circle.isLineSecant to be upper-case.\r
+- Changed IsPrime to accept only one number at a time.\r
+- Changed NewShape's type to Capitals.\r
+\r
+Related to code:\r
+- Added more accuarate comments.\r
+- Made code more DRY.\r
+- Made code monkey-patchable and saved space (by declaring all functions as local values then inserted them into a large table.\r
+\r
+TODO:\r
+- Make LineIntersectsPolygon return where intersection occurs.\r
+- Ditto with PolygonIntersectsPolygon.\r
+- Add:\r
+  - Frequency\r
+  - Binomial Probability\r
+  - Standard Deviation\r
+  - Conditional Probability\r
+\r
+\r
+Not as accurately maintained before 2.0.2\r
+-----------------------------------------\r
+\r
+2.0.2\r
+-----\r
+- Cleaned up code, mostly.\r
+\r
+2.0.1\r
+-----\r
+- Bug fixes, mlib.shape:remove & demos added.\r
+\r
+2.0.0\r
+-----\r
+- Added mlib.shape and various bug fixes.\r
+\r
+2.0.0\r
+-----\r
+- Made mlib.shape and made numberous bug fixes.\r
+\r
+1.9.4\r
+-----\r
+- Made mlib.math.prime faster and removed ability to test multiple numbers at once. Thanks Robin!\r
+\r
+1.9.3\r
+-----\r
+- Fixed polygon.area and polygon.centroid\r
+\r
+1.9.2\r
+-----\r
+- Updated to LOVE 0.9.0.\r
+\r
+1.9.1\r
+-----\r
+- Made mlib.line.closestPoint able to take either two points on the slope or the slope and intercept.\r
+\r
+1.9.0\r
+-----\r
+- Added mlib.lineSegmentIntersects (no affiliation with previous one (changed to mlib.line.segment.intersect)) and mlib.line.closestPoint\r
+\r
+1.8.3\r
+-----\r
+- Changed naming mechanism to be more organized.\r
+\r
+1.8.2\r
+-----\r
+- "Fixed" mlib.lineSegmentsIntersect AGAIN!!!!  :x\r
+\r
+1.8.1\r
+-----\r
+- Removed a print statement.\r
+\r
+1.8.0\r
+-----\r
+- mlib.pointInPolygon added\r
+\r
+1.7.5\r
+-----\r
+- mlib.lineSegmentsIntersect vertical lines fixed again. This time for real. I promise... or hope, at least...  :P\r
+\r
+1.7.4\r
+-----\r
+- mlib.lineSegmentsIntersect vertical parallels fixed\r
+\r
+1.7.3\r
+-----\r
+- mlib.lineSegmentsIntersect parallels fixed\r
+\r
+1.7.2\r
+-----\r
+- mlib.lineSegmentsIntersect now handles vertical lines\r
+\r
+1.7.1\r
+-----\r
+- mlib.lineSegmentsIntersect now returns the two places in between where the line segments begin to intersect.\r
+\r
+1.7.0\r
+-----\r
+- Added mlib.circlesIntersect, mlib.pointOnLineSegment, mlib.linesIntersect, and mlib.lineSegmentsIntersect\r
+\r
+1.6.1\r
+-----\r
+- Employed usage of summations for mlib.getPolygonArea and mlib.getPolygonCentroid and removed area as an argument for mlib.getPolygonCentroid.\r
+\r
+1.6.0\r
+-----\r
+- Added several functions.\r
+\r
+1.5.0\r
+-----\r
+- Made lots of changes to syntax to make it easier to use (hopefully). I also put out specs.\r
+\r
+1.4.1\r
+-----\r
+- Localized mlib. Thanks, Yonaba!\r
+\r
+1.4.0\r
+-----\r
+- Added mlib.getPolygonCentroid (gets the midpoint of a non-self-intersecting polygons)\r
+\r
+1.3.2\r
+-----\r
+- Made mlib.getPrime take tables as arguments, so you can check all the values of a table.\r
+\r
+1.3.1\r
+-----\r
+- Changed name method to mlib.getPolygonArea\r
+\r
+1.3.0\r
+-----\r
+- Added mlib.get_polygon_area and removed mlib.get_convex_area and mlib.get_triangle_area since they are repetitive.\r
+\r
+1.2.2\r
+-----\r
+- Made functions return faster, functions that previously returned tables now return multiple arguments.\r
+\r
+1.2.1\r
+-----\r
+- Localized functions, made tables acceptable as arguments, refined function speed, mlib.get_mode now returns number most repeated as well as how many times.\r
+\r
+1.2.0\r
+-----\r
+- Added mlib.get_angle\r
+\r
+1.1.0\r
+-----\r
+- Added mlib.get_convex_area\r
+\r
+1.0.4\r
+-----\r
+- Fixed get_mode to handle bimodials.\r
+\r
+1.0.3\r
+-----\r
+- Prime Checker optimized (hopefully final update on this.)\r
+\r
+1.0.2\r
+-----\r
+- Prime checker now works! (At least to 1000. I haven't tested any\r
+further)\r
+\r
+1.0.1\r
+-----\r
+- 'Fixed' the prime checker\r
+\r
+1.0.0\r
+-----\r
+- Initial release\r
diff --git a/main/plugin/windfield/mlib/LICENSE.md b/main/plugin/windfield/mlib/LICENSE.md
new file mode 100644 (file)
index 0000000..0e7071e
--- /dev/null
@@ -0,0 +1,17 @@
+Copyright (c) 2015 Davis Claiborne\r
+\r
+This software is provided 'as-is', without any express or implied\r
+warranty. In no event will the authors be held liable for any damages\r
+arising from the use of this software.\r
+\r
+Permission is granted to anyone to use this software for any purpose,\r
+including commercial applications, and to alter it and redistribute it\r
+freely, subject to the following restrictions:\r
+\r
+1. The origin of this software must not be misrepresented; you must not\r
+   claim that you wrote the original software. If you use this software\r
+   in a product, an acknowledgement in the product documentation would be\r
+   appreciated but is not required.\r
+2. Altered source versions must be plainly marked as such, and must not be\r
+   misrepresented as being the original software.\r
+3. This notice may not be removed or altered from any source distribution.\r
diff --git a/main/plugin/windfield/mlib/README.md b/main/plugin/windfield/mlib/README.md
new file mode 100644 (file)
index 0000000..a5efed3
--- /dev/null
@@ -0,0 +1,890 @@
+MLib\r
+====\r
+\r
+__MLib__ is a math and shape-intersection detection library written in Lua. It's aim is to be __robust__ and __easy to use__.\r
+\r
+__NOTE:__ \r
+- I am (slowly) working on completely rewriting this in order to be easier to use and less bug-prone. You can check out the progress [here](../../tree/dev).\r
+- I am currently slowing development of MLib while moving over to helping with [CPML](https://github.com/excessive/cpml). To discuss this, please comment [here](../../issues/12).\r
+\r
+If you are looking for a library that handles updating/collision responses for you, take a look at [hxdx](https://github.com/adonaac/hxdx). It uses MLib functions as well as Box2d to handle physics calculations. \r
+\r
+## Downloading\r
+You can download the latest __stable__ version of MLib by downloading the latest [release](../../releases/).\r
+You can download the latest __working__ version of MLib by downloading the latest [commit](../../commits/master/). Documentation will __only__ be updated upon releases, not upon commits.\r
+\r
+## Implementing\r
+To use MLib, simply place [mlib.lua](mlib.lua) inside the desired folder in your project. Then use the `require 'path.to.mlib'` to use any of the functions.\r
+\r
+## Examples\r
+If you don't have [LÖVE](https://love2d.org/) installed, you can download the .zip of the demo from the [Executables](Examples/Executables) folder and extract and run the .exe that way.\r
+You can see some examples of the code in action [here](Examples).\r
+All examples are done using the *awesome* engine of [LÖVE](https://love2d.org/).\r
+To run them properly, download the [.love file](Examples/LOVE) and install LÖVE to your computer.\r
+After that, make sure you set .love files to open with "love.exe".\r
+For more, see [here](https://love2d.org/).\r
+\r
+## When should I use MLib?\r
+- If you need to know exactly where two objects intersect.\r
+- If you need general mathematical equations to be done.\r
+- If you need very precise details about point intersections.\r
+\r
+## When should I __not__ use MLib?\r
+- All of the objects in a platformer, or other game, for instance, should not be registered with MLib. Only ones that need very specific information.\r
+- When you don't need precise information/odd shapes.\r
+\r
+## Specs\r
+#### For Windows\r
+If you run Windows and have Telescope in `%USERPROFILE%\Documents\GitHub` (you can also manually change the path in [test.bat](test.bat)) you can simply run [test.bat](test.bat) and it will display the results, and then clean up after it's finished.\r
+\r
+#### Default\r
+Alternatively, you can find the tests [here](spec.lua). Keep in mind that you may need to change certain semantics to suit your OS.\r
+You can run them via [Telescope](https://github.com/norman/telescope/) and type the following command in the command-line of the root folder:\r
+```\r
+tsc -f specs.lua\r
+```\r
+If that does not work, you made need to put a link to Lua inside of the folder for `telescope` and run the following command:\r
+```\r
+lua tsc -f specs.lua\r
+```\r
+If you encounter further errors, try to run the command line as an administrator (usually located in `C:\Windows\System32\`), then right-click on `cmd.exe` and select `Run as administrator`, then do\r
+```\r
+cd C:\Path\to\telescope\\r
+```\r
+And __then__ run one of the above commands. If none of those work, just take my word for it that all the tests pass and look at this picture.\r
+![Success](Reference Pictures/Success.png)\r
+\r
+## Functions\r
+- [mlib.line](#mlibline)\r
+  - [mlib.line.checkPoint](#mliblinecheckpoint)\r
+  - [mlib.line.getClosestPoint](#mliblinegetclosestpoint)\r
+  - [mlib.line.getYIntercept](#mliblinegetintercept)\r
+  - [mlib.line.getIntersection](#mliblinegetintersection)\r
+  - [mlib.line.getLength](#mliblinegetlength)\r
+  - [mlib.line.getMidpoint](#mliblinegetmidpoint)\r
+  - [mlib.line.getPerpendicularSlope](#mliblinegetperpendicularslope)\r
+  - [mlib.line.getSegmentIntersection](#mliblinegetsegmentintersection)\r
+  - [mlib.line.getSlope](#mliblinegetslope)\r
+- [mlib.segment](#mlibsegment)\r
+  - [mlib.segment.checkPoint](#mlibsegmentcheckpoint)\r
+  - [mlib.segment.getPerpendicularBisector](#mlibsegmentgetperpendicularbisector)\r
+  - [mlib.segment.getIntersection](#mlibsegmentgetintersection)\r
+- [mlib.polygon](#mlibpolygon)\r
+  - [mlib.polygon.checkPoint](#mlibpolygoncheckpoint)\r
+  - [mlib.polygon.getCentroid](#mlibpolygongetcentroid)\r
+  - [mlib.polygon.getCircleIntersection](#mlibpolygongetcircleintersection)\r
+  - [mlib.polygon.getLineIntersection](#mlibpolygongetlineintersection)\r
+  - [mlib.polygon.getPolygonArea](#mlibpolygongetpolygonarea)\r
+  - [mlib.polygon.getPolygonIntersection](#mlibpolygongetpolygonintersection)\r
+  - [mlib.polygon.getSegmentIntersection](#mlibpolygongetsegmentintersection)\r
+  - [mlib.polygon.getSignedPolygonArea](#mlibpolygongetsignedpolygonarea)\r
+  - [mlib.polygon.getTriangleHeight](#mlibpolygongettriangleheight)\r
+  - [mlib.polygon.isCircleInside](#mlibpolygoniscircleinside)\r
+  - [mlib.polygon.isCircleCompletelyInside](#mlibpolygoniscirclecompletelyinside)\r
+  - [mlib.polygon.isPolygonInside](#mlibpolygonispolygoninside)\r
+  - [mlib.polygon.isPolygonCompletelyInside](#mlibpolygonispolygoncompletelyinside)\r
+  - [mlib.polygon.isSegmentInside](#mlibpolygonissegmentinside)\r
+  - [mlib.polygon.isSegmentCompletelyInside](#mlibpolygonissegmentcompletelyinside)\r
+- [mlib.circle](#mlibcircle)\r
+  - [mlib.circle.checkPoint](#mlibcirclecheckpoint)\r
+  - [mlib.circle.getArea](#mlibcirclegetarea)\r
+  - [mlib.circle.getCircleIntersection](#mlibcirclegetcircleintersection)\r
+  - [mlib.circle.getCircumference](#mlibcirclegetcircumference)\r
+  - [mlib.circle.getLineIntersection](#mlibcirclegetlineintersection)\r
+  - [mlib.circle.getSegmentIntersection](#mlibcirclegetsegmentintersection)\r
+  - [mlib.circle.isCircleCompletelyInside](#mlibcircleiscirclecompletelyinside)\r
+  - [mlib.circle.isCircleCompletelyInsidePolygon](#mlibcircleiscirclecompletelyinsidepolygon)\r
+  - [mlib.circle.isPointOnCircle](#mlibcircleispointoncircle)\r
+  - [mlib.circle.isPolygonCompletelyInside](#mlibcircleispolygoncompletelyinside)\r
+- [mlib.statistics](#mlibstatistics)\r
+  - [mlib.statistics.getCentralTendency](#mlibstatisticsgetcentraltendency)\r
+  - [mlib.statistics.getDispersion](#mlibstatisticsgetdispersion)\r
+  - [mlib.statistics.getMean](#mlibstatisticsgetmean)\r
+  - [mlib.statistics.getMedian](#mlibstatisticsgetmedian)\r
+  - [mlib.statistics.getMode](#mlibstatisticsgetmode)\r
+  - [mlib.statistics.getRange](#mlibstatisticsgetrange)\r
+  - [mlib.statistics.getStandardDeviation](#mlibstatisticsgetstandarddeviation)\r
+  - [mlib.statistics.getVariance](#mlibstatisticsgetvariance)\r
+  - [mlib.statistics.getVariationRatio](#mlibstatisticsgetvariationratio)\r
+- [mlib.math](#mlibmath)\r
+  - [mlib.math.getAngle](#mlibmathgetangle)\r
+  - [mlib.math.getPercentage](#mlibmathgetpercentage)\r
+  - [mlib.math.getPercentOfChange](#mlibmathgetpercentofchange)\r
+  - [mlib.math.getQuadraticRoots](#mlibmathgetquadraticroots)\r
+  - [mlib.math.getRoot](#mlibmathgetroot)\r
+  - [mlib.math.getSummation](#mlibmathgetsummation)\r
+  - [mlib.math.isPrime](#mlibmathisprime)\r
+  - [mlib.math.round](#mlibmathround)\r
+- [Aliases](#aliases)\r
+\r
+#### mlib.line\r
+- Deals with linear aspects, such as slope and length.\r
+\r
+##### mlib.line.checkPoint\r
+- Checks if a point lies on a line.\r
+- Synopsis:\r
+  - `onPoint = mlib.line.checkPoint( px, px, x1, y1, x2, y2 )`\r
+- Arguments:\r
+  - `px`, `py`: Numbers. The x and y coordinates of the point being tested.\r
+  - `x1`, `y1`, `x2`, `y2`: Numbers. Two x and y coordinates of the line being tested.\r
+- Returns:\r
+  - `onPoint`: Boolean.\r
+    - `true` if the point is on the line.\r
+       - `false` if it does not.\r
+- Notes:\r
+  - You cannot use the format `mlib.line.checkPoint( px, px, slope, intercept )` because this would lead to errors on vertical lines.\r
+\r
+##### mlib.line.getClosestPoint\r
+- Gives the closest point to a line.\r
+- Synopses:\r
+  - `cx, cy = mlib.line.getClosestPoint( px, py, x1, y1, x2, y2 )`\r
+  - `cx, cy = mlib.line.getClosestPoint( px, py, slope, intercept )`\r
+- Arguments:\r
+  - `x`, `y`: Numbers. The x and y coordinates of the point.\r
+  - `x1`, `y1`, `x2`, `y2`: Numbers. Two x and y coordinates on the line.\r
+  - `slope`, `intercept`:\r
+    - Numbers. The slope and y-intercept of the line.\r
+       - Booleans (`false`). The slope and y-intercept of a vertical line.\r
+- Returns:\r
+  - `cx`, `cy`: Numbers. The closest points that lie on the line to the point.\r
+\r
+##### mlib.line.getYIntercept\r
+- Gives y-intercept of the line.\r
+- Synopses:\r
+  - `intercept, isVertical = mlib.line.getYIntercept( x1, y1, x2, y2 )`\r
+  - `intercept, isVertical = mlib.line.getYIntercept( x1, y1, slope )`\r
+- Arguments:\r
+  - `x1`, `y1`, `x2`, `y2`: Numbers. Two x and y coordinates that lie on the line.\r
+  - `slope`:\r
+    - Number. The slope of the line.\r
+- Returns:\r
+  - `intercept`:\r
+    - Number. The y-intercept of the line.\r
+    - Number. The `x1` coordinate of the line if the line is vertical.\r
+  - `isVertical`:\r
+    - Boolean. `true` if the line is vertical, `false` if the line is not vertical.\r
+\r
+##### mlib.line.getIntersection\r
+- Gives the intersection of two lines.\r
+- Synopses:\r
+  - `x, y = mlib.line.getIntersection( x1, y1, x2, y2, x3, y3, x4, y4 )`\r
+  - `x, y = mlib.line.getIntersection( slope1, intercept1, x3, y3, x4, y4 )`\r
+  - `x, y = mlib.line.getIntersection( slope1, intercept1, slope2, intercept2 )`\r
+- Arguments:\r
+  - `x1`, `y1`, `x2`, `y2`: Numbers. Two x and y coordinates that lie on the first line.\r
+  - `x3`, `y3`, `x4`, `y4`: Numbers. Two x and y coordinates that lie on the second line.\r
+  - `slope1`, `intercept1`:\r
+    - Numbers. The slope and y-intercept of the first line.\r
+       - Booleans (`false`). The slope and y-intercept of the first line (if the first line is vertical).\r
+  - `slope2`, `intercept2`:\r
+    - Numbers. The slope and y-intercept of the second line.\r
+       - Booleans (`false`). The slope and y-intercept of the second line (if the second line is vertical).\r
+- Returns:\r
+  - `x`, `y`:\r
+    - Numbers. The x and y coordinate where the lines intersect.\r
+       - Boolean:\r
+         - `true`, `nil`: The lines are collinear.\r
+         - `false`, `nil`: The lines are parallel and __not__ collinear.\r
+\r
+##### mlib.line.getLength\r
+- Gives the distance between two points.\r
+- Synopsis:\r
+  - `length = mlib.line.getLength( x1, y1, x2, y2 )\r
+- Arguments:\r
+  - `x1`, `y1`, `x2`, `y2`: Numbers. Two x and y coordinates.\r
+- Returns:\r
+  - `length`: Number. The distance between the two points.\r
+\r
+##### mlib.line.getMidpoint\r
+- Gives the midpoint of two points.\r
+- Synopsis:\r
+  - `x, y = mlib.line.getMidpoint( x1, y1, x2, y2 )`\r
+- Arguments:\r
+  - `x1`, `y1`, `x2`, `y2`: Numbers. Two x and y coordinates.\r
+- Returns:\r
+  - `x`, `y`: Numbers. The midpoint x and y coordinates.\r
+\r
+##### mlib.line.getPerpendicularSlope\r
+- Gives the perpendicular slope of a line.\r
+- Synopses:\r
+  - `perpSlope = mlib.line.getPerpendicularSlope( x1, y1, x2, y2 )`\r
+  - `perpSlope = mlib.line.getPerpendicularSlope( slope )`\r
+- Arguments:\r
+  - `x1`, `y1`, `x2`, `y2`: Numbers. Two x and y coordinates.\r
+  - `slope`: Number. The slope of the line.\r
+- Returns:\r
+  - `perpSlope`:\r
+    - Number. The perpendicular slope of the line.\r
+       - Boolean (`false`). The perpendicular slope of the line (if the original line was horizontal).\r
+\r
+##### mlib.line.getSegmentIntersection\r
+- Gives the intersection of a line segment and a line.\r
+- Synopses:\r
+  - `x1, y1, x2, y2 = mlib.line.getSegmentIntersection( x1, y1, x2, y2, x3, y3, x4, y4 )`\r
+  - `x1, y1, x2, y2 = mlib.line.getSegmentIntersection( x1, y1, x2, y2, slope, intercept )`\r
+- Arguments:\r
+  - `x1`, `y1`, `x2`, `y2`: Numbers. Two x and y coordinates that lie on the line segment.\r
+  - `x3`, `y3`, `x4`, `y4`: Numbers. Two x and y coordinates that lie on the line.\r
+  - `slope`, `intercept`:\r
+    - Numbers. The slope and y-intercept of the the line.\r
+       - Booleans (`false`). The slope and y-intercept of the line (if the line is vertical).\r
+- Returns:\r
+  - `x1`, `y1`, `x2`, `y2`:\r
+    - Number, Number, Number, Number.\r
+         - The points of the line segment if the line and segment are collinear.\r
+       - Number, Number, Boolean (`nil`), Boolean (`nil`).\r
+         - The coordinate of intersection if the line and segment intersect and are not collinear.\r
+       - Boolean (`false`), Boolean (`nil`), Boolean (`nil`),\r
+         - Boolean (`nil`). If the line and segment don't intersect.\r
+\r
+##### mlib.line.getSlope\r
+- Gives the slope of a line.\r
+- Synopsis:\r
+  - `slope = mlib.line.getSlope( x1, y1, x2, y2 )\r
+- Arguments:\r
+  - `x1`, `y1`, `x2`, `y2`: Numbers. Two x and y coordinates.\r
+- Returns:\r
+  - `slope`:\r
+    - Number. The slope of the line.\r
+       - Boolean (`false`). The slope of the line (if the line is vertical).\r
+\r
+#### mlib.segment\r
+- Deals with line segments.\r
+\r
+##### mlib.segment.checkPoint\r
+- Checks if a point lies on a line segment.\r
+- Synopsis:\r
+  - `onSegment = mlib.segment.checkPoint( px, py, x1 y1, x2, y2 )`\r
+- Arguments:\r
+  - `px`, `py`: Numbers. The x and y coordinates of the point being checked.\r
+  - `x1`, `y1`, `x2`, `y2`: Numbers. Two x and y coordinates.\r
+- Returns:\r
+  - `onSegment`: Boolean.\r
+    - `true` if the point lies on the line segment.\r
+       - `false` if the point does not lie on the line segment.\r
+\r
+##### mlib.segment.getPerpendicularBisector\r
+- Gives the perpendicular bisector of a line.\r
+- Synopsis:\r
+  - `x, y, slope = mlib.segment.getPerpendicularBisector( x1, y1, x2, y2 )`\r
+- Arguments:\r
+  - `x1`, `y1`, `x2`, `y2`: Numbers. Two x and y coordinates.\r
+- Returns:\r
+  - `x`, `y`: Numbers. The midpoint of the line.\r
+  - `slope`:\r
+    - Number. The perpendicular slope of the line.\r
+       - Boolean (`false`). The perpendicular slope of the line (if the original line was horizontal).\r
+\r
+##### mlib.segment.getIntersection\r
+- Checks if two line segments intersect.\r
+- Synopsis:\r
+  - `cx1, cy1, cx2, cy2 = mlib.segment.getIntersection( x1, y1, x2, y2, x3, y3 x4, y4 )`\r
+- Arguments:\r
+  - `x1`, `y1`, `x2`, `y2`: Numbers. Two x and y coordinates of the first line segment.\r
+  - `x3`, `y3`, `x4`, `y4`: Numbers. Two x and y coordinates of the second line segment.\r
+- Returns:\r
+  - `cx1`, `cy1`, `cx2`, `cy2`:\r
+    - Number, Number, Number, Number.\r
+         - The points of the resulting intersection if the line segments are collinear.\r
+       - Number, Number, Boolean (`nil`), Boolean (`nil`).\r
+         - The point of the resulting intersection if the line segments are not collinear.\r
+       - Boolean (`false`), Boolean (`nil`), Boolean (`nil`) , Boolean (`nil`).\r
+         - If the line segments don't intersect.\r
+\r
+#### mlib.polygon\r
+- Handles aspects involving polygons.\r
+\r
+##### mlib.polygon.checkPoint\r
+- Checks if a point is inside of a polygon.\r
+- Synopses:\r
+  - `inPolygon = mlib.polygon.checkPoint( px, py, vertices )`\r
+  - `inPolygon = mlib.polygon.checkPoint( px, py, ... )`\r
+- Arguments:\r
+  - `px`, `py`: Numbers. The x and y coordinate of the point being checked.\r
+  - `vertices`: Table. The vertices of the polygon in the format `{ x1, y1, x2, y2, x3, y3, ... }`\r
+  - `...`: Numbers. The x and y coordinates of the polygon. (Same as using `unpack( vertices )`)\r
+- Returns:\r
+  - `inPolygon`: Boolean.\r
+    - `true` if the point is inside the polygon.\r
+       - `false` if the point is not inside the polygon.\r
+\r
+##### mlib.polygon.getCentroid\r
+- Returns the centroid of the polygon.\r
+- Synopses:\r
+  - `cx, cy = mlib.polygon.getCentroid( vertices )`\r
+  - `cx, cy = mlib.polygon.getCentroid( ... )`\r
+- Arguments:\r
+  - `vertices`: Table. The vertices of the polygon in the format `{ x1, y1, x2, y2, x3, y3, ... }`\r
+  - `...`: Numbers. The x and y coordinates of the polygon. (Same as using `unpack( vertices )`)\r
+- Returns:\r
+  - `cx`, `cy`: Numbers. The x and y coordinates of the centroid.\r
+\r
+##### mlib.polygon.getCircleIntersection\r
+- Returns the coordinates of where a circle intersects a polygon.\r
+- Synopses:\r
+  - `intersections = mlib.polygon.getCircleIntersection( cx, cy, radius, vertices )`\r
+  - `intersections = mlib.polygon.getCircleIntersection( cx, cy, radius, ... )\r
+- Arguments:\r
+  - `cx`, `cy`: Number. The coordinates of the center of the circle.\r
+  - `radius`: Number. The radius of the circle.\r
+  - `vertices`: Table. The vertices of the polygon in the format `{ x1, y1, x2, y2, x3, y3, ... }`\r
+  - `...`: Numbers. The x and y coordinates of the polygon. (Same as using `unpack( vertices )`)\r
+- Returns:\r
+  - `intersections`: Table. Contains the intersections and type.\r
+- Example:\r
+```lua\r
+local tab = _.polygon.getCircleIntersection( 5, 5, 1, 4, 4, 6, 4, 6, 6, 4, 6 )\r
+for i = 1, # tab do\r
+       print( i .. ':', unpack( tab[i] ) )\r
+end\r
+-- 1:  tangent         5               4\r
+-- 2:  tangent         6               5\r
+-- 3:  tangent         5               6\r
+-- 4:  tagnent         4               5\r
+```\r
+- For more see [mlib.circle.getSegmentIntersection](#mlibcirclegetsegmentintersection) or the [specs](spec.lua# L676)\r
+\r
+##### mlib.polygon.getLineIntersection\r
+- Returns the coordinates of where a line intersects a polygon.\r
+- Synopses:\r
+  - `intersections = mlib.polygon.getLineIntersection( x1, y1, x2, y2, vertices )`\r
+  - `intersections = mlib.polygon.getLineIntersection( x1, y1, x2, y2, ... )\r
+- Arguments:\r
+  - `x1`, `y1`, `x2`, `y2`: Numbers. Two x and y coordinates.\r
+  - `vertices`: Table. The vertices of the polygon in the format `{ x1, y1, x2, y2, x3, y3, ... }`\r
+  - `...`: Numbers. The x and y coordinates of the polygon. (Same as using `unpack( vertices )`)\r
+- Returns:\r
+  - `intersections`: Table. Contains the intersections.\r
+- Notes:\r
+  - With collinear lines, they are actually broken up. i.e. `{ 0, 4, 0, 0 }` would become `{ 0, 4 }, { 0, 0 }`.\r
+\r
+##### mlib.polygon.getPolygonArea\r
+- Gives the area of a polygon.\r
+- Synopses:\r
+  - `area = mlib.polygon.getArea( vertices )`\r
+  - `area = mlib.polygon.getArea( ... )\r
+- Arguments:\r
+  - `vertices`: Table. The vertices of the polygon in the format `{ x1, y1, x2, y2, x3, y3, ... }`\r
+  - `...`: Numbers. The x and y coordinates of the polygon. (Same as using `unpack( vertices )`)\r
+- Returns:\r
+  - `area`: Number. The area of the polygon.\r
+\r
+##### mlib.polygon.getPolygonIntersection\r
+- Gives the intersection of two polygons.\r
+- Synopsis:\r
+  - `intersections = mlib.polygon.getPolygonIntersections( polygon1, polygon2 )`\r
+- Arguments:\r
+  - `polygon1`: Table. The vertices of the first polygon in the format `{ x1, y1, x2, y2, x3, y3, ... }`\r
+  - `polygon2`: Table. The vertices of the second polygon in the format `{ x1, y1, x2, y2, x3, y3, ... }`\r
+- Returns:\r
+  - `intersections`: Table. A table of the points of intersection.\r
+\r
+##### mlib.polygon.getSegmentIntersection\r
+- Returns the coordinates of where a line segmeing intersects a polygon.\r
+- Synopses:\r
+  - `intersections = mlib.polygon.getSegmentIntersection( x1, y1, x2, y2, vertices )`\r
+  - `intersections = mlib.polygon.getSegmentIntersection( x1, y1, x2, y2, ... )\r
+- Arguments:\r
+  - `x1`, `y1`, `x2`, `y2`: Numbers. Two x and y coordinates.\r
+  - `vertices`: Table. The vertices of the polygon in the format `{ x1, y1, x2, y2, x3, y3, ... }`\r
+  - `...`: Numbers. The x and y coordinates of the polygon. (Same as using `unpack( vertices )`)\r
+- Returns:\r
+  - `intersections`: Table. Contains the intersections.\r
+- Notes:\r
+  - With collinear line segments, they are __not__ broken up. See the [specs](spec.lua# L508) for more.\r
+\r
+##### mlib.polygon.getSignedPolygonArea\r
+- Gets the signed area of the polygon. If the points are ordered counter-clockwise the area is positive. If the points are ordered clockwise the number is negative.\r
+- Synopses:\r
+  - `area = mlib.polygon.getLineIntersection( vertices )`\r
+  - `area = mlib.polygon.getLineIntersection( ... )\r
+- Arguments:\r
+  - `vertices`: Table. The vertices of the polygon in the format `{ x1, y1, x2, y2, x3, y3, ... }`\r
+  - `...`: Numbers. The x and y coordinates of the polygon. (Same as using `unpack( vertices )`)\r
+- Returns:\r
+  - `area`: Number. The __signed__ area of the polygon. If the points are ordered counter-clockwise the area is positive. If the points are ordered clockwise the number is negative.\r
+\r
+##### mlib.polygon.getTriangleHeight\r
+- Gives the height of a triangle.\r
+- Synopses:\r
+  - `height = mlib.polygon.getTriangleHeigh( base, x1, y1, x2, y2, x3, y3 )`\r
+  - `height = mlib.polygon.getTriangleHeight( base, area )`\r
+- Arguments:\r
+  - `base`: Number. The length of the base of the triangle.\r
+  - `x1`, `y1`, `x2`, `y2`, `x3`, `y3`: Numbers. The x and y coordinates of the triangle.\r
+  - `area`: Number. The regular area of the triangle. __Not__ the signed area.\r
+- Returns:\r
+  - `height`: Number. The height of the triangle.\r
+\r
+##### mlib.polygon.isCircleInside\r
+- Checks if a circle is inside the polygon.\r
+- Synopses:\r
+  - `inPolygon = mlib.polygon.isCircleInside( cx, cy, radius, vertices )`\r
+  - `inPolygon = mlib.polygon.isCircleInside( cx, cy, radius, ... )`\r
+- Arguments:\r
+  - `cx`, `cy`: Numbers. The x and y coordinates for the center of the circle.\r
+  - `radius`: Number. The radius of the circle.\r
+  - `vertices`: Table. The vertices of the polygon in the format `{ x1, y1, x2, y2, x3, y3, ... }`\r
+  - `...`: Numbers. The x and y coordinates of the polygon. (Same as using `unpack( vertices )`)\r
+- Returns:\r
+  - `inPolygon`: Boolean.\r
+    - `true` if the circle is inside the polygon.\r
+       - `false` if the circle is not inside the polygon.\r
+- Notes:\r
+  - Only returns true if the center of the circle is inside the circle.\r
+\r
+##### mlib.polygon.isCircleCompletelyInside\r
+- Checks if a circle is completely inside the polygon.\r
+- Synopses:\r
+  - `inPolygon = mlib.polygon.isCircleCompletelyInside( cx, cy, radius, vertices )`\r
+  - `inPolygon = mlib.polygon.isCircleCompletelyInside( cx, cy, radius, ... )`\r
+- Arguments:\r
+  - `cx`, `cy`: Numbers. The x and y coordinates for the center of the circle.\r
+  - `radius`: Number. The radius of the circle.\r
+  - `vertices`: Table. The vertices of the polygon in the format `{ x1, y1, x2, y2, x3, y3, ... }`\r
+  - `...`: Numbers. The x and y coordinates of the polygon. (Same as using `unpack( vertices )`)\r
+- Returns:\r
+  - `inPolygon`: Boolean.\r
+    - `true` if the circle is __completely__ inside the polygon.\r
+       - `false` if the circle is not inside the polygon.\r
+\r
+##### mlib.polygon.isPolygonInside\r
+- Checks if a polygon is inside a polygon.\r
+- Synopsis:\r
+  - `inPolygon = mlib.polygon.isPolygonInside( polygon1, polygon2 )`\r
+- Arguments:\r
+  - `polygon1`: Table. The vertices of the first polygon in the format `{ x1, y1, x2, y2, x3, y3, ... }`\r
+  - `polygon2`: Table. The vertices of the second polygon in the format `{ x1, y1, x2, y2, x3, y3, ... }`\r
+- Returns:\r
+  - `inPolygon`: Boolean.\r
+    - `true` if the `polygon2` is inside of `polygon1`.\r
+       - `false` if `polygon2` is not inside of `polygon2`.\r
+- Notes:\r
+  - Returns true as long as any of the line segments of `polygon2` are inside of the `polygon1`.\r
+\r
+##### mlib.polygon.isPolygonCompletelyInside\r
+- Checks if a polygon is completely inside a polygon.\r
+- Synopsis:\r
+  - `inPolygon = mlib.polygon.isPolygonCompletelyInside( polygon1, polygon2 )`\r
+- Arguments:\r
+  - `polygon1`: Table. The vertices of the first polygon in the format `{ x1, y1, x2, y2, x3, y3, ... }`\r
+  - `polygon2`: Table. The vertices of the second polygon in the format `{ x1, y1, x2, y2, x3, y3, ... }`\r
+- Returns:\r
+  - `inPolygon`: Boolean.\r
+    - `true` if the `polygon2` is __completely__ inside of `polygon1`.\r
+       - `false` if `polygon2` is not inside of `polygon2`.\r
+\r
+##### mlib.polygon.isSegmentInside\r
+- Checks if a line segment is inside a polygon.\r
+- Synopses:\r
+  - `inPolygon = mlib.polygon.isSegmentInside( x1, y1, x2, y2, vertices )`\r
+  - `inPolygon = mlib.polygon.isSegmentInside( x1, y1, x2, y2, ... )`\r
+- Arguments:\r
+  - `x1`, `y1`, `x2`, `y2`: Numbers. The x and y coordinates of the line segment.\r
+  - `vertices`: Table. The vertices of the polygon in the format `{ x1, y1, x2, y2, x3, y3, ... }`\r
+  - `...`: Numbers. The x and y coordinates of the polygon. (Same as using `unpack( vertices )`)\r
+- Returns:\r
+  - `inPolygon`: Boolean.\r
+    - `true` if the line segment is inside the polygon.\r
+       - `false` if the line segment is not inside the polygon.\r
+- Note:\r
+  - Only one of the points has to be in the polygon to be considered 'inside' of the polygon.\r
+  - This is really just a faster version of [mlib.polygon.getPolygonIntersection](#mlibpolygongetpolygonintersection) that does not give the points of intersection.\r
+\r
+##### mlib.polygon.isSegmentCompletelyInside\r
+- Checks if a line segment is completely inside a polygon.\r
+- Synopses:\r
+  - `inPolygon = mlib.polygon.isSegmentCompletelyInside( x1, y1, x2, y2, vertices )`\r
+  - `inPolygon = mlib.polygon.isSegmentCompletelyInside( x1, y1, x2, y2, ... )`\r
+- Arguments:\r
+  - `x1`, `y1`, `x2`, `y2`: Numbers. The x and y coordinates of the line segment.\r
+  - `vertices`: Table. The vertices of the polygon in the format `{ x1, y1, x2, y2, x3, y3, ... }`\r
+  - `...`: Numbers. The x and y coordinates of the polygon. (Same as using `unpack( vertices )`)\r
+- Returns:\r
+  - `inPolygon`: Boolean.\r
+    - `true` if the line segment is __completely__ inside the polygon.\r
+       - `false` if the line segment is not inside the polygon.\r
+\r
+#### mlib.circle\r
+- Handles aspects involving circles.\r
+\r
+##### mlib.circle.checkPoint\r
+- Checks if a point is on the inside or on the edge the circle.\r
+- Synopsis:\r
+  - `inCircle = mlib.circle.checkPoint( px, px, cx, cy, radius )`\r
+- Arguments:\r
+  - `px`, `py`: Numbers. The x and y coordinates of the point being tested.\r
+  - `cx`, `cy`: Numbers. The x and y coordinates of the center of the circle.\r
+  - `radius`: Number. The radius of the circle.\r
+- Returns:\r
+  - `inCircle`: Boolean.\r
+    - `true` if the point is inside or on the circle.\r
+       - `false` if the point is outside of the circle.\r
+\r
+##### mlib.circle.getArea\r
+- Gives the area of a circle.\r
+- Synopsis:\r
+  - `area = mlib.circle.getArea( radius )`\r
+- Arguments:\r
+  - `radius`: Number. The radius of the circle.\r
+- Returns:\r
+  - `area`: Number. The area of the circle.\r
+\r
+##### mlib.circle.getCircleIntersection\r
+- Gives the intersections of two circles.\r
+- Synopsis:\r
+  - `intersections = mlib.circle.getCircleIntersection( c1x, c1y, radius1, c2x, c2y, radius2 )\r
+- Arguments:\r
+  - `c1x`, `c1y`: Numbers. The x and y coordinate of the first circle.\r
+  - `radius1`: Number. The radius of the first circle.\r
+  - `c2x`, `c2y`: Numbers. The x and y coordinate of the second circle.\r
+  - `radius2`: Number. The radius of the second circle.\r
+- Returns:\r
+  - `intersections`: Table. A table that contains the type and where the circle collides. See the [specs](spec.lua# L698) for more.\r
+\r
+##### mlib.circle.getCircumference\r
+- Returns the circumference of a circle.\r
+- Synopsis:\r
+  - `circumference = mlib.circle.getCircumference( radius )`\r
+- Arguments:\r
+  - `radius`: Number. The radius of the circle.\r
+- Returns:\r
+  - `circumference`: Number. The circumference of a circle.\r
+\r
+##### mlib.circle.getLineIntersection\r
+- Returns the intersections of a circle and a line.\r
+- Synopsis:\r
+  - `intersections = mlib.circle.getLineIntersections( cx, cy, radius, x1, y1, x2, y2 )`\r
+- Arguments:\r
+  - `cx`, `cy`: Numbers. The x and y coordinates for the center of the circle.\r
+  - `radius`: Number. The radius of the circle.\r
+  - `x1`, `y1`, `x2`, `y2`: Numbers. Two x and y coordinates the lie on the line.\r
+- Returns:\r
+  - `intersections`: Table. A table with the type and where the intersections happened. Table is formatted:\r
+    - `type`, `x1`, `y1`, `x2`, `y2`\r
+         - String (`'secant'`), Number, Number, Number, Number\r
+           - The numbers are the x and y coordinates where the line intersects the circle.\r
+         - String (`'tangent'`), Number, Number, Boolean (`nil`), Boolean (`nil`)\r
+           - `x1` and `x2` represent where the line intersects the circle.\r
+         - Boolean (`false`), Boolean (`nil`), Boolean (`nil`), Boolean (`nil`), Boolean (`nil`)\r
+           - No intersection.\r
+    - For more see the [specs](spec.lua# L660).\r
+\r
+##### mlib.circle.getSegmentIntersection\r
+- Returns the intersections of a circle and a line segment.\r
+- Synopsis:\r
+  - `intersections = mlib.circle.getSegmentIntersections( cx, cy, radius, x1, y1, x2, y2 )`\r
+- Arguments:\r
+  - `cx`, `cy`: Numbers. The x and y coordinates for the center of the circle.\r
+  - `radius`: Number. The radius of the circle.\r
+  - `x1`, `y1`, `x2`, `y2`: Numbers. The two x and y coordinates of the line segment.\r
+- Returns:\r
+  - `intersections`: Table. A table with the type and where the intersections happened. Table is formatted:\r
+    - `type`, `x1`, `y1`, `x2`, `y2`\r
+         - String (`'chord'`), Number, Number, Number, Number\r
+           - The numbers are the x and y coordinates where the line segment is on both edges of the circle.\r
+         - String (`'enclosed'`), Number, Number, Number, Number\r
+           - The numbers are the x and y coordinates of the line segment if it is fully inside of the circle.\r
+         - String (`'secant'`), Number, Number, Number, Number\r
+           - The numbers are the x and y coordinates where the line segment intersects the circle.\r
+         - String (`'tangent'`), Number, Number, Boolean (`nil`), Boolean (`nil`)\r
+           - `x1` and `x2` represent where the line segment intersects the circle.\r
+         - Boolean (`false`), Boolean (`nil`), Boolean (`nil`), Boolean (`nil`), Boolean (`nil`)\r
+           - No intersection.\r
+    - For more see the [specs](spec.lua# L676).\r
+\r
+##### mlib.circle.isCircleCompletelyInside\r
+- Checks if one circle is completely inside of another circle.\r
+- Synopsis:\r
+  - `completelyInside = mlib.circle.isCircleCompletelyInside( c1x, c1y, c1radius, c2x, c2y, c2radius )`\r
+- Arguments:\r
+  - `c1x`, `c1y`: Numbers. The x and y coordinates of the first circle.\r
+  - `c1radius`: Number. The radius of the first circle.\r
+  - `c2x`, `c2y`: Numbers. The x and y coordinates of the second circle.\r
+  - `c2radius`: Number. The radius of the second circle.\r
+- Returns:\r
+  - `completelyInside`: Boolean.\r
+    - `true` if circle1 is inside of circle2.\r
+       - `false` if circle1 is not __completely__ inside of circle2.\r
+\r
+##### mlib.circle.isCircleCompletelyInsidePolygon\r
+- Checks if a circle is completely inside the polygon.\r
+- Synopses:\r
+  - `inPolygon = mlib.polygon.isCircleCompletelyInside( cx, cy, radius, vertices )`\r
+  - `inPolygon = mlib.polygon.isCircleCompletelyInside( cx, cy, radius, ... )`\r
+- Arguments:\r
+  - `cx`, `cy`: Numbers. The x and y coordinates for the center of the circle.\r
+  - `radius`: Number. The radius of the circle.\r
+  - `vertices`: Table. The vertices of the polygon in the format `{ x1, y1, x2, y2, x3, y3, ... }`\r
+  - `...`: Numbers. The x and y coordinates of the polygon. (Same as using `unpack( vertices )`)\r
+- Returns:\r
+  - `inPolygon`: Boolean.\r
+    - `true` if the circle is __completely__ inside the polygon.\r
+       - `false` if the circle is not inside the polygon.\r
+\r
+##### mlib.circle.isPointOnCircle\r
+- Checks if a point is __exactly__ on the edge of the circle.\r
+- Synopsis:\r
+  - `onCircle = mlib.circle.checkPoint( px, px, cx, cy, radius )`\r
+- Arguments:\r
+  - `px`, `py`: Numbers. The x and y coordinates of the point being tested.\r
+  - `cx`, `cy`: Numbers. The x and y coordinates of the center of the circle.\r
+  - `radius`: Number. The radius of the circle.\r
+- Returns:\r
+  - `onCircle`: Boolean.\r
+    - `true` if the point is on the circle.\r
+       - `false` if the point is on the inside or outside of the circle.\r
+- Notes:\r
+  - Will return false if the point is inside __or__ outside of the circle.\r
+\r
+##### mlib.circle.isPolygonCompletelyInside\r
+- Checks if a polygon is completely inside of a circle.\r
+- Synopsis:\r
+  - `completelyInside = mlib.circle.isPolygonCompletelyInside( circleX, circleY, circleRadius, vertices )`\r
+  - `completelyInside = mlib.circle.isPolygonCompletelyInside( circleX, circleY, circleRadius, ... )`\r
+- Arguments:\r
+  - `circleX`, `circleY`: Numbers. The x and y coordinates of the circle.\r
+  - `circleRadius`: Number. The radius of the circle.\r
+  - `vertices`: Table. A table containing all of the vertices of the polygon.\r
+  - `...`: Numbers. All of the points of the polygon.\r
+- Returns:\r
+  - `completelyInside`: Boolean.\r
+    - `true` if the polygon is inside of the circle.\r
+       - `false` if the polygon is not __completely__ inside of the circle.\r
+\r
+#### mlib.statistics\r
+- Handles statistical aspects of math.\r
+\r
+##### mlib.statistics.getCentralTendency\r
+- Gets the central tendency of the data.\r
+- Synopses:\r
+  - `modes, occurrences, median, mean = mlib.statistics.getCentralTendency( data )`\r
+  - `modes, occurrences, median, mean = mlib.statistics.getCentralTendency( ... )`\r
+- Arguments:\r
+  - `data`: Table. A table containing the values of data.\r
+  - `...`: Numbers. All of the numbers in the data set.\r
+- Returns:\r
+  - `modes, occurrences`: Table, Number. The modes of the data and the number of times it occurs. See [mlib.statistics.getMode](#mlibstatisticsgetmode).\r
+  - `median`: Number. The median of the data set.\r
+  - `mean`: Number. The mean of the data set.\r
+\r
+##### mlib.statistics.getDispersion\r
+- Gets the dispersion of the data.\r
+- Synopses:\r
+  - `variationRatio, range, standardDeviation = mlib.statistics.getDispersion( data )`\r
+  - `variationRatio, range, standardDeviation = mlib.statistics.getDispersion( ... )`\r
+- Arguments:\r
+  - `data`: Table. A table containing the values of data.\r
+  - `...`: Numbers. All of the numbers in the data set.\r
+- Returns:\r
+  - `variationRatio`: Number. The variation ratio of the data set.\r
+  - `range`: Number. The range of the data set.\r
+  - `standardDeviation`: Number. The standard deviation of the data set.\r
+\r
+##### mlib.statistics.getMean\r
+- Gets the arithmetic mean of the data.\r
+- Synopses:\r
+  - `mean = mlib.statistics.getMean( data )`\r
+  - `mean = mlib.statistics.getMean( ... )`\r
+- Arguments:\r
+  - `data`: Table. A table containing the values of data.\r
+  - `...`: Numbers. All of the numbers in the data set.\r
+- Returns:\r
+  - `mean`: Number. The arithmetic mean of the data set.\r
+\r
+##### mlib.statistics.getMedian\r
+- Gets the median of the data set.\r
+- Synopses:\r
+  - `median = mlib.statistics.getMedian( data )`\r
+  - `median = mlib.statistics.getMedian( ... )`\r
+- Arguments:\r
+  - `data`: Table. A table containing the values of data.\r
+  - `...`: Numbers. All of the numbers in the data set.\r
+- Returns:\r
+  - `median`: Number. The median of the data.\r
+\r
+##### mlib.statistics.getMode\r
+- Gets the mode of the data set.\r
+- Synopses:\r
+  - `mode, occurrences = mlib.statistics.getMode( data )`\r
+  - `mode, occurrences = mlib.statistics.getMode( ... )`\r
+- Arguments:\r
+  - `data`: Table. A table containing the values of data.\r
+  - `...`: Numbers. All of the numbers in the data set.\r
+- Returns:\r
+  - `mode`: Table. The mode(s) of the data.\r
+  - `occurrences`: Number. The number of time the mode(s) occur.\r
+\r
+##### mlib.statistics.getRange\r
+- Gets the range of the data set.\r
+- Synopses:\r
+  - `range = mlib.statistics.getRange( data )`\r
+  - `range = mlib.statistics.getRange( ... )`\r
+- Arguments:\r
+  - `data`: Table. A table containing the values of data.\r
+  - `...`: Numbers. All of the numbers in the data set.\r
+- Returns:\r
+  - `range`: Number. The range of the data.\r
+\r
+##### mlib.statistics.getStandardDeviation\r
+- Gets the standard deviation of the data.\r
+- Synopses:\r
+  - `standardDeviation = mlib.statistics.getStandardDeviation( data )`\r
+  - `standardDeviation = mlib.statistics.getStandardDeviation( ... )`\r
+- Arguments:\r
+  - `data`: Table. A table containing the values of data.\r
+  - `...`: Numbers. All of the numbers in the data set.\r
+- Returns:\r
+  - `standardDeviation`: Number. The standard deviation of the data set.\r
+\r
+##### mlib.statistics.getVariance\r
+- Gets the variation of the data.\r
+- Synopses:\r
+  - `variance = mlib.statistics.getVariance( data )`\r
+  - `variance = mlib.statistics.getVariance( ... )`\r
+- Arguments:\r
+  - `data`: Table. A table containing the values of data.\r
+  - `...`: Numbers. All of the numbers in the data set.\r
+- Returns:\r
+  - `variance`: Number. The variation of the data set.\r
+\r
+##### mlib.statistics.getVariationRatio\r
+- Gets the variation ratio of the data.\r
+- Synopses:\r
+  - `variationRatio = mlib.statistics.getVariationRatio( data )`\r
+  - `variationRatio = mlib.statistics.getVariationRatio( ... )`\r
+- Arguments:\r
+  - `data`: Table. A table containing the values of data.\r
+  - `...`: Numbers. All of the numbers in the data set.\r
+- Returns:\r
+  - `variationRatio`: Number. The variation ratio of the data set.\r
+\r
+#### mlib.math\r
+- Miscellaneous functions that have no home.\r
+\r
+##### mlib.math.getAngle\r
+- Gets the angle between three points.\r
+- Synopsis:\r
+  - `angle = mlib.math.getAngle( x1, y1, x2, y2, x3, y3 )`\r
+- Arguments:\r
+  - `x1`, `y1`: Numbers. The x and y coordinates of the first point.\r
+  - `x2`, `y2`: Numbers. The x and y coordinates of the vertex of the two points.\r
+  - `x3`, `y3`: Numbers. The x and y coordinates of the second point.\r
+\r
+##### mlib.math.getPercentage\r
+- Gets the percentage of a number.\r
+- Synopsis:\r
+  - `percentage = mlib.math.getPercentage( percent, number )`\r
+- Arguments:\r
+  - `percent`: Number. The decimal value of the percent (i.e. 100% is 1, 50% is .5).\r
+  - `number`: Number. The number to get the percentage of.\r
+- Returns:\r
+  - `percentage`: Number. The `percent`age or `number`.\r
+\r
+##### mlib.math.getPercentOfChange\r
+- Gets the percent of change from one to another.\r
+- Synopsis:\r
+  - `change = mlib.math.getPercentOfChange( old, new )`\r
+- Arguments:\r
+  - `old`: Number. The original number.\r
+  - `new`: Number. The new number.\r
+- Returns:\r
+  - `change`: Number. The percent of change from `old` to `new`.\r
+\r
+##### mlib.math.getQuadraticRoots\r
+- Gets the quadratic roots of the the equation.\r
+- Synopsis:\r
+  - `root1, root2 = mlib.math.getQuadraticRoots( a, b, c )`\r
+- Arguments:\r
+  - `a`, `b`, `c`: Numbers. The a, b, and c values of the equation `a * x ^ 2 + b * x ^ 2 + c`.\r
+- Returns:\r
+  - `root1`, `root2`: Numbers. The roots of the equation (where `a * x ^ 2 + b * x ^ 2 + c = 0`).\r
+\r
+##### mlib.math.getRoot\r
+- Gets the `n`th root of a number.\r
+- Synopsis:\r
+  - `x = mlib.math.getRoot( number, root )`\r
+- Arguments:\r
+  - `number`: Number. The number to get the root of.\r
+  - `root`: Number. The root.\r
+- Returns:\r
+  - `x`: The `root`th root of `number`.\r
+- Example:\r
+```lua\r
+local a = mlib.math.getRoot( 4, 2 ) -- Same as saying 'math.pow( 4, .5 )' or 'math.sqrt( 4 )' in this case.\r
+local b = mlib.math.getRoot( 27, 3 )\r
+\r
+print( a, b ) --> 2, 3\r
+```\r
+  - For more, see the [specs](spec.lua# L860).\r
+\r
+##### mlib.math.getSummation\r
+- Gets the summation of numbers.\r
+- Synopsis:\r
+  - `summation = mlib.math.getSummation( start, stop, func )`\r
+- Arguments:\r
+  - `start`: Number. The number at which to start the summation.\r
+  - `stop`: Number. The number at which to stop the summation.\r
+  - `func`: Function. The method to add the numbers.\r
+    - Arguments:\r
+         - `i`: Number. Index.\r
+         - `previous`: Table. The previous values used.\r
+- Returns:\r
+  - `Summation`: Number. The summation of the numbers.\r
+  - For more, see the [specs](spec.lua# L897).\r
+\r
+##### mlib.math.isPrime\r
+- Checks if a number is prime.\r
+- Synopsis:\r
+  - `isPrime = mlib.math.isPrime( x )`\r
+- Arguments:\r
+  - `x`: Number. The number to check if it's prime.\r
+- Returns:\r
+  - `isPrime`: Boolean.\r
+    - `true` if the number is prime.\r
+       - `false` if the number is not prime.\r
+\r
+##### mlib.math.round\r
+- Rounds a number to the given decimal place.\r
+- Synopsis:\r
+  - `rounded = mlib.math.round( number, [place] )\r
+- Arguments:\r
+  - `number`: Number. The number to round.\r
+  - `place (1)`: Number. The decimal place to round to. Defaults to 1.\r
+- Returns:\r
+  - The rounded number.\r
+  - For more, see the [specs](spec.lua# L881).\r
+\r
+#### Aliases\r
+| Alias                                         | Corresponding Function                                                            |\r
+| ----------------------------------------------|:---------------------------------------------------------------------------------:|\r
+| milb.line.getDistance                         | [mlib.line.getLength](#mliblinegetlength)                                         |\r
+| mlib.line.getCircleIntersection               | [mlib.circle.getLineIntersection](#mlibcirclegetlineintersection)                 |\r
+| milb.line.getPolygonIntersection              | [mlib.polygon.getLineIntersection](#mlibpolygongetlineintersection)               |\r
+| mlib.line.getLineIntersection                 | [mlib.line.getIntersection](#mliblinegetintersection)                             |\r
+| mlib.segment.getCircleIntersection            | [mlib.circle.getSegmentIntersection](#mlibcirclegetsegmentintersection)           |\r
+| milb.segment.getPolygonIntersection           | [mlib.pollygon.getSegmentIntersection](#mlibpollygongetsegmentintersection)       |\r
+| mlib.segment.getLineIntersection              | [mlib.line.getSegmentIntersection](#mliblinegetsegmentintersection)               |\r
+| mlib.segment.getSegmentIntersection           | [mlib.segment.getIntersection](#mlibsegmentgetintersection)                       |\r
+| milb.segment.isSegmentCompletelyInsideCircle  | [mlib.circle.isSegmentCompletelyInside](#mlibcircleissegmentcompletelyinside)     |\r
+| mlib.segment.isSegmentCompletelyInsidePolygon | [mlib.polygon.isSegmentCompletelyInside](#mlibpolygonissegmentcompletelyinside)   |\r
+| mlib.circle.getPolygonIntersection            | [mlib.polygon.getCircleIntersection](#mlibpolygongetcircleintersection)           |\r
+| mlib.circle.isCircleInsidePolygon             | [mlib.polygon.isCircleInside](#mlibpolygoniscircleinside)                         |\r
+| mlib.circle.isCircleCompletelyInsidePolygon   | [mlib.polygon.isCircleCompletelyInside](#mlibpolygoniscirclecompletelyinside)     |\r
+| mlib.polygon.isCircleCompletelyOver           | [mlib.circleisPolygonCompletelyInside](#mlibcircleispolygoncompletelyinside)      |\r
+\r
+## License\r
+A math library made in Lua\r
+copyright (C) 2014 Davis Claiborne\r
+This program is free software; you can redistribute it and/or modify\r
+it under the terms of the GNU General Public License as published by\r
+the Free Software Foundation; either version 2 of the License, or\r
+(at your option) any later version.\r
+This program is distributed in the hope that it will be useful,\r
+but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
+GNU General Public License for more details.\r
+You should have received a copy of the GNU General Public License along\r
+with this program; if not, write to the Free Software Foundation, Inc.,\r
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\r
+Contact me at davisclaib at gmail.com\r
diff --git a/main/plugin/windfield/mlib/mlib.lua b/main/plugin/windfield/mlib/mlib.lua
new file mode 100644 (file)
index 0000000..76067c6
--- /dev/null
@@ -0,0 +1,1152 @@
+--[[ License\r
+       A math library made in Lua\r
+       copyright (C) 2014 Davis Claiborne\r
+       This program is free software; you can redistribute it and/or modify\r
+       it under the terms of the GNU General Public License as published by\r
+       the Free Software Foundation; either version 2 of the License, or\r
+       (at your option) any later version.\r
+       This program is distributed in the hope that it will be useful,\r
+       but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+       MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
+       GNU General Public License for more details.\r
+       You should have received a copy of the GNU General Public License along\r
+       with this program; if not, write to the Free Software Foundation, Inc.,\r
+       51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\r
+       Contact me at davisclaib@gmail.com\r
+]]\r
+\r
+-- Local Utility Functions ---------------------- {{{\r
+local unpack = table.unpack or unpack\r
+\r
+-- Used to handle variable-argument functions and whether they are passed as func{ table } or func( unpack( table ) )\r
+local function checkInput( ... )\r
+       local input = {}\r
+       if type( ... ) ~= 'table' then input = { ... } else input = ... end\r
+       return input\r
+end\r
+\r
+-- Deals with floats / verify false false values. This can happen because of significant figures.\r
+local function checkFuzzy( number1, number2 )\r
+       return ( number1 - .00001 <= number2 and number2 <= number1 + .00001 )\r
+end\r
+\r
+-- Remove multiple occurrences from a table.\r
+local function removeDuplicatePairs( tab )\r
+       for index1 = #tab, 1, -1 do\r
+               local first = tab[index1]\r
+               for index2 = #tab, 1, -1 do\r
+                       local second = tab[index2]\r
+                       if index1 ~= index2 then\r
+                               if type( first[1] ) == 'number' and type( second[1] ) == 'number' and type( first[2] ) == 'number' and type( second[2] ) == 'number' then\r
+                                       if checkFuzzy( first[1], second[1] ) and checkFuzzy( first[2], second[2] ) then\r
+                                               table.remove( tab, index1 )\r
+                                       end\r
+                               elseif first[1] == second[1] and first[2] == second[2] then\r
+                                       table.remove( tab, index1 )\r
+                               end\r
+                       end\r
+               end\r
+       end\r
+       return tab\r
+end\r
+\r
+\r
+local function removeDuplicates4Points( tab )\r
+    for index1 = #tab, 1, -1 do\r
+        local first = tab[index1]\r
+        for index2 = #tab, 1, -1 do\r
+            local second = tab[index2]\r
+            if index1 ~= index2 then\r
+                if type( first[1] ) ~= type( second[1] ) then return false end\r
+                if type( first[2] ) == 'number' and type( second[2] ) == 'number' and type( first[3] ) == 'number' and type( second[3] ) == 'number' then\r
+                    if checkFuzzy( first[2], second[2] ) and checkFuzzy( first[3], second[3] ) then\r
+                        table.remove( tab, index1 )\r
+                    end\r
+                elseif checkFuzzy( first[1], second[1] ) and checkFuzzy( first[2], second[2] ) and checkFuzzy( first[3], second[3] ) then\r
+                    table.remove( tab, index1 )\r
+                end\r
+            end\r
+        end\r
+    end\r
+    return tab\r
+end\r
+\r
+\r
+-- Add points to the table.\r
+local function addPoints( tab, x, y )\r
+    tab[#tab + 1] = x\r
+    tab[#tab + 1] = y\r
+end\r
+\r
+-- Like removeDuplicatePairs but specifically for numbers in a flat table\r
+local function removeDuplicatePointsFlat( tab )\r
+    for i = #tab, 1 -2 do\r
+        for ii = #tab - 2, 3, -2 do\r
+            if i ~= ii then\r
+                local x1, y1 = tab[i], tab[i + 1]\r
+                local x2, y2 = tab[ii], tab[ii + 1]\r
+                if checkFuzzy( x1, x2 ) and checkFuzzy( y1, y2 ) then\r
+                    table.remove( tab, ii ); table.remove( tab, ii + 1 )\r
+                end\r
+            end\r
+        end\r
+    end\r
+    return tab\r
+end\r
+\r
+\r
+-- Check if input is actually a number\r
+local function validateNumber( n )\r
+       if type( n ) ~= 'number' then return false\r
+       elseif n ~= n then return false -- nan\r
+       elseif math.abs( n ) == math.huge then return false\r
+       else return true end\r
+end\r
+\r
+local function cycle( tab, index ) return tab[( index - 1 ) % #tab + 1] end\r
+\r
+local function getGreatestPoint( points, offset )\r
+    offset = offset or 1\r
+    local start = 2 - offset\r
+    local greatest = points[start]\r
+    local least = points[start]\r
+    for i = 2, #points / 2 do\r
+        i = i * 2 - offset\r
+        if points[i] > greatest then\r
+            greatest = points[i]\r
+        end\r
+        if points[i] < least then\r
+            least = points[i]\r
+        end\r
+    end\r
+    return greatest, least\r
+end\r
+\r
+local function isWithinBounds( min, num, max )\r
+    return num >= min and num <= max\r
+end\r
+\r
+local function distance2( x1, y1, x2, y2 ) -- Faster since it does not use math.sqrt\r
+       local dx, dy = x1 - x2, y1 - y2\r
+       return dx * dx + dy * dy\r
+end -- }}}\r
+\r
+-- Points -------------------------------------- {{{\r
+local function rotatePoint( x, y, rotation, ox, oy )\r
+    ox, oy = ox or 0, oy or 0\r
+    return ( x - ox ) * math.cos( rotation ) + ox - ( y - oy ) * math.sin( rotation ), ( x - ox ) * math.sin( rotation ) + ( y - oy ) * math.cos( rotation ) + oy\r
+end\r
+\r
+local function scalePoint( x, y, scale, ox, oy )\r
+    ox, oy = ox or 0, oy or 0\r
+    return ( x - ox ) * scale + ox, ( y - oy ) * scale + oy\r
+end\r
+-- }}}\r
+\r
+-- Lines --------------------------------------- {{{\r
+-- Returns the length of a line.\r
+local function getLength( x1, y1, x2, y2 )\r
+       local dx, dy = x1 - x2, y1 - y2\r
+       return math.sqrt( dx * dx + dy * dy )\r
+end\r
+\r
+-- Gives the midpoint of a line.\r
+local function getMidpoint( x1, y1, x2, y2 )\r
+       return ( x1 + x2 ) / 2, ( y1 + y2 ) / 2\r
+end\r
+\r
+-- Gives the slope of a line.\r
+local function getSlope( x1, y1, x2, y2 )\r
+       if checkFuzzy( x1, x2 ) then return false end -- Technically it's undefined, but this is easier to program.\r
+       return ( y1 - y2 ) / ( x1 - x2 )\r
+end\r
+\r
+-- Gives the perpendicular slope of a line.\r
+-- x1, y1, x2, y2\r
+-- slope\r
+local function getPerpendicularSlope( ... )\r
+       local input = checkInput( ... )\r
+       local slope\r
+\r
+       if #input ~= 1 then\r
+               slope = getSlope( unpack( input ) )\r
+       else\r
+               slope = unpack( input )\r
+       end\r
+\r
+       if not slope then return 0 -- Vertical lines become horizontal.\r
+       elseif checkFuzzy( slope, 0 ) then return false -- Horizontal lines become vertical.\r
+    else return -1 / slope end\r
+end\r
+\r
+-- Gives the y-intercept of a line.\r
+-- x1, y1, x2, y2\r
+-- x1, y1, slope\r
+local function getYIntercept( x, y, ... )\r
+       local input = checkInput( ... )\r
+       local slope\r
+\r
+       if #input == 1 then\r
+               slope = input[1]\r
+       else\r
+               slope = getSlope( x, y, unpack( input ) )\r
+       end\r
+\r
+       if not slope then return x, true end -- This way we have some information on the line.\r
+       return y - slope * x, false\r
+end\r
+\r
+-- Gives the intersection of two lines.\r
+-- slope1, slope2, x1, y1, x2, y2\r
+-- slope1, intercept1, slope2, intercept2\r
+-- x1, y1, x2, y2, x3, y3, x4, y4\r
+local function getLineLineIntersection( ... )\r
+       local input = checkInput( ... )\r
+       local x1, y1, x2, y2, x3, y3, x4, y4\r
+       local slope1, intercept1\r
+       local slope2, intercept2\r
+       local x, y\r
+\r
+       if #input == 4 then -- Given slope1, intercept1, slope2, intercept2.\r
+               slope1, intercept1, slope2, intercept2 = unpack( input )\r
+\r
+               -- Since these are lines, not segments, we can use arbitrary points, such as ( 1, y ), ( 2, y )\r
+               y1 = slope1 and slope1 * 1 + intercept1 or 1\r
+               y2 = slope1 and slope1 * 2 + intercept1 or 2\r
+               y3 = slope2 and slope2 * 1 + intercept2 or 1\r
+               y4 = slope2 and slope2 * 2 + intercept2 or 2\r
+               x1 = slope1 and ( y1 - intercept1 ) / slope1 or intercept1\r
+               x2 = slope1 and ( y2 - intercept1 ) / slope1 or intercept1\r
+               x3 = slope2 and ( y3 - intercept2 ) / slope2 or intercept2\r
+               x4 = slope2 and ( y4 - intercept2 ) / slope2 or intercept2\r
+       elseif #input == 6 then -- Given slope1, intercept1, and 2 points on the other line.\r
+               slope1, intercept1 = input[1], input[2]\r
+               slope2 = getSlope( input[3], input[4], input[5], input[6] )\r
+               intercept2 = getYIntercept( input[3], input[4], input[5], input[6] )\r
+\r
+               y1 = slope1 and slope1 * 1 + intercept1 or 1\r
+               y2 = slope1 and slope1 * 2 + intercept1 or 2\r
+               y3 = input[4]\r
+               y4 = input[6]\r
+               x1 = slope1 and ( y1 - intercept1 ) / slope1 or intercept1\r
+               x2 = slope1 and ( y2 - intercept1 ) / slope1 or intercept1\r
+               x3 = input[3]\r
+               x4 = input[5]\r
+       elseif #input == 8 then -- Given 2 points on line 1 and 2 points on line 2.\r
+               slope1 = getSlope( input[1], input[2], input[3], input[4] )\r
+               intercept1 = getYIntercept( input[1], input[2], input[3], input[4] )\r
+               slope2 = getSlope( input[5], input[6], input[7], input[8] )\r
+               intercept2 = getYIntercept( input[5], input[6], input[7], input[8] )\r
+\r
+               x1, y1, x2, y2, x3, y3, x4, y4 = unpack( input )\r
+       end\r
+\r
+       if not slope1 and not slope2 then -- Both are vertical lines\r
+               if x1 == x3 then -- Have to have the same x positions to intersect\r
+                       return true\r
+               else\r
+                       return false\r
+               end\r
+       elseif not slope1 then -- First is vertical\r
+               x = x1 -- They have to meet at this x, since it is this line's only x\r
+               y = slope2 and slope2 * x + intercept2 or 1\r
+       elseif not slope2 then -- Second is vertical\r
+               x = x3 -- Vice-Versa\r
+               y = slope1 * x + intercept1\r
+       elseif checkFuzzy( slope1, slope2 ) then -- Parallel (not vertical)\r
+               if checkFuzzy( intercept1, intercept2 ) then -- Same intercept\r
+                       return true\r
+               else\r
+                       return false\r
+               end\r
+       else -- Regular lines\r
+               x = ( -intercept1 + intercept2 ) / ( slope1 - slope2 )\r
+               y = slope1 * x + intercept1\r
+       end\r
+\r
+       return x, y\r
+end\r
+\r
+-- Gives the closest point on a line to a point.\r
+-- perpendicularX, perpendicularY, x1, y1, x2, y2\r
+-- perpendicularX, perpendicularY, slope, intercept\r
+local function getClosestPoint( perpendicularX, perpendicularY, ... )\r
+       local input = checkInput( ... )\r
+       local x, y, x1, y1, x2, y2, slope, intercept\r
+\r
+       if #input == 4 then -- Given perpendicularX, perpendicularY, x1, y1, x2, y2\r
+               x1, y1, x2, y2 = unpack( input )\r
+               slope = getSlope( x1, y1, x2, y2 )\r
+               intercept = getYIntercept( x1, y1, x2, y2 )\r
+       elseif #input == 2 then -- Given perpendicularX, perpendicularY, slope, intercept\r
+               slope, intercept = unpack( input )\r
+        x1, y1 = 1, slope and slope * 1 + intercept or 1 -- Need x1 and y1 in case of vertical/horizontal lines.\r
+       end\r
+\r
+       if not slope then -- Vertical line\r
+               x, y = x1, perpendicularY -- Closest point is always perpendicular.\r
+       elseif checkFuzzy( slope, 0 ) then -- Horizontal line\r
+               x, y = perpendicularX, y1\r
+       else\r
+               local perpendicularSlope = getPerpendicularSlope( slope )\r
+               local perpendicularIntercept = getYIntercept( perpendicularX, perpendicularY, perpendicularSlope )\r
+               x, y = getLineLineIntersection( slope, intercept, perpendicularSlope, perpendicularIntercept )\r
+       end\r
+\r
+       return x, y\r
+end\r
+\r
+-- Gives the intersection of a line and a line segment.\r
+-- x1, y1, x2, y2, x3, y3, x4, y4\r
+-- x1, y1, x2, y2, slope, intercept\r
+local function getLineSegmentIntersection( x1, y1, x2, y2, ... )\r
+       local input = checkInput( ... )\r
+\r
+       local slope1, intercept1, x, y, lineX1, lineY1, lineX2, lineY2\r
+       local slope2, intercept2 = getSlope( x1, y1, x2, y2 ), getYIntercept( x1, y1, x2, y2 )\r
+\r
+       if #input == 2 then -- Given slope, intercept\r
+               slope1, intercept1 = input[1], input[2]\r
+        lineX1, lineY1 = 1, slope1 and slope1 + intercept1\r
+        lineX2, lineY2 = 2, slope1 and slope1 * 2 + intercept1\r
+       else -- Given x3, y3, x4, y4\r
+        lineX1, lineY1, lineX2, lineY2 = unpack( input )\r
+               slope1 = getSlope( unpack( input ) )\r
+               intercept1 = getYIntercept( unpack( input ) )\r
+       end\r
+\r
+       if not slope1 and not slope2 then -- Vertical lines\r
+               if checkFuzzy( x1, lineX1 ) then\r
+                       return x1, y1, x2, y2\r
+               else\r
+                       return false\r
+               end\r
+       elseif not slope1 then -- slope1 is vertical\r
+               x, y = input[1], slope2 * input[1] + intercept2\r
+       elseif not slope2 then -- slope2 is vertical\r
+               x, y = x1, slope1 * x1 + intercept1\r
+       else\r
+               x, y = getLineLineIntersection( slope1, intercept1, slope2, intercept2 )\r
+       end\r
+\r
+       local length1, length2, distance\r
+       if x == true then -- Lines are collinear.\r
+               return x1, y1, x2, y2\r
+       elseif x then -- There is an intersection\r
+               length1, length2 = getLength( x1, y1, x, y ), getLength( x2, y2, x, y )\r
+               distance = getLength( x1, y1, x2, y2 )\r
+       else -- Lines are parallel but not collinear.\r
+               if checkFuzzy( intercept1, intercept2 ) then\r
+                       return x1, y1, x2, y2\r
+               else\r
+                       return false\r
+               end\r
+       end\r
+\r
+       if length1 <= distance and length2 <= distance then return x, y else return false end\r
+end\r
+\r
+-- Checks if a point is on a line.\r
+-- Does not support the format using slope because vertical lines would be impossible to check.\r
+local function checkLinePoint( x, y, x1, y1, x2, y2 )\r
+       local m = getSlope( x1, y1, x2, y2 )\r
+       local b = getYIntercept( x1, y1, m )\r
+\r
+       if not m then -- Vertical\r
+               return checkFuzzy( x, x1 )\r
+       end\r
+       return checkFuzzy( y, m * x + b )\r
+end -- }}}\r
+\r
+-- Segment -------------------------------------- {{{\r
+-- Gives the perpendicular bisector of a line.\r
+local function getPerpendicularBisector( x1, y1, x2, y2 )\r
+       local slope = getSlope( x1, y1, x2, y2 )\r
+       local midpointX, midpointY = getMidpoint( x1, y1, x2, y2 )\r
+       return midpointX, midpointY, getPerpendicularSlope( slope )\r
+end\r
+\r
+-- Gives whether or not a point lies on a line segment.\r
+local function checkSegmentPoint( px, py, x1, y1, x2, y2 )\r
+       -- Explanation around 5:20: https://www.youtube.com/watch?v=A86COO8KC58\r
+       local x = checkLinePoint( px, py, x1, y1, x2, y2 )\r
+       if not x then return false end\r
+\r
+       local lengthX = x2 - x1\r
+       local lengthY = y2 - y1\r
+\r
+       if checkFuzzy( lengthX, 0 ) then -- Vertical line\r
+               if checkFuzzy( px, x1 ) then\r
+                       local low, high\r
+                       if y1 > y2 then low = y2; high = y1\r
+                       else low = y1; high = y2 end\r
+\r
+                       if py >= low and py <= high then return true\r
+                       else return false end\r
+               else\r
+                       return false\r
+               end\r
+       elseif checkFuzzy( lengthY, 0 ) then -- Horizontal line\r
+               if checkFuzzy( py, y1 ) then\r
+                       local low, high\r
+                       if x1 > x2 then low = x2; high = x1\r
+                       else low = x1; high = x2 end\r
+\r
+                       if px >= low and px <= high then return true\r
+                       else return false end\r
+               else\r
+                       return false\r
+               end\r
+       end\r
+\r
+       local distanceToPointX = ( px - x1 )\r
+       local distanceToPointY = ( py - y1 )\r
+       local scaleX = distanceToPointX / lengthX\r
+       local scaleY = distanceToPointY / lengthY\r
+\r
+       if ( scaleX >= 0 and scaleX <= 1 ) and ( scaleY >= 0 and scaleY <= 1 ) then -- Intersection\r
+               return true\r
+       end\r
+       return false\r
+end\r
+\r
+-- Gives the point of intersection between two line segments.\r
+local function getSegmentSegmentIntersection( x1, y1, x2, y2, x3, y3, x4, y4 )\r
+       local slope1, intercept1 = getSlope( x1, y1, x2, y2 ), getYIntercept( x1, y1, x2, y2 )\r
+       local slope2, intercept2 = getSlope( x3, y3, x4, y4 ), getYIntercept( x3, y3, x4, y4 )\r
+\r
+       if ( ( slope1 and slope2 ) and checkFuzzy( slope1, slope2 ) ) or ( not slope1 and not slope2 ) then -- Parallel lines\r
+               if checkFuzzy( intercept1, intercept2 ) then -- The same lines, possibly in different points.\r
+                       local points = {}\r
+                       if checkSegmentPoint( x1, y1, x3, y3, x4, y4 ) then addPoints( points, x1, y1 ) end\r
+                       if checkSegmentPoint( x2, y2, x3, y3, x4, y4 ) then addPoints( points, x2, y2 ) end\r
+                       if checkSegmentPoint( x3, y3, x1, y1, x2, y2 ) then addPoints( points, x3, y3 ) end\r
+                       if checkSegmentPoint( x4, y4, x1, y1, x2, y2 ) then addPoints( points, x4, y4 ) end\r
+\r
+                       points = removeDuplicatePointsFlat( points )\r
+                       if #points == 0 then return false end\r
+                       return unpack( points )\r
+               else\r
+                       return false\r
+               end\r
+       end\r
+\r
+       local x, y = getLineLineIntersection( x1, y1, x2, y2, x3, y3, x4, y4 )\r
+       if x and checkSegmentPoint( x, y, x1, y1, x2, y2 ) and checkSegmentPoint( x, y, x3, y3, x4, y4 ) then\r
+               return x, y\r
+       end\r
+       return false\r
+end -- }}}\r
+\r
+-- Math ----------------------------------------- {{{\r
+-- Get the root of a number (i.e. the 2nd (square) root of 4 is 2)\r
+local function getRoot( number, root )\r
+       return number ^ ( 1 / root )\r
+end\r
+\r
+-- Checks if a number is prime.\r
+local function isPrime( number )\r
+       if number < 2 then return false end\r
+\r
+       for i = 2, math.sqrt( number ) do\r
+               if number % i == 0 then\r
+                       return false\r
+               end\r
+       end\r
+       return true\r
+end\r
+\r
+-- Rounds a number to the xth decimal place (round( 3.14159265359, 4 ) --> 3.1416)\r
+local function round( number, place )\r
+       local pow = 10 ^ ( place or 0 )\r
+    return math.floor( number * pow + .5 ) / pow\r
+end\r
+\r
+-- Gives the summation given a local function\r
+local function getSummation( start, stop, func )\r
+       local returnValues = {}\r
+       local sum = 0\r
+       for i = start, stop do\r
+               local value = func( i, returnValues )\r
+               returnValues[i] = value\r
+               sum = sum + value\r
+       end\r
+       return sum\r
+end\r
+\r
+-- Gives the percent of change.\r
+local function getPercentOfChange( old, new )\r
+       if old == 0 and new == 0 then\r
+        return 0\r
+       else\r
+               return ( new - old ) / math.abs( old )\r
+       end\r
+end\r
+\r
+-- Gives the percentage of a number.\r
+local function getPercentage( percent, number )\r
+       return percent * number\r
+end\r
+\r
+-- Returns the quadratic roots of an equation.\r
+local function getQuadraticRoots( a, b, c )\r
+       local discriminant = b ^ 2 - ( 4 * a * c )\r
+       if discriminant < 0 then return false end\r
+       discriminant = math.sqrt( discriminant )\r
+       local denominator = ( 2 * a )\r
+       return ( -b - discriminant ) / denominator, ( -b + discriminant ) / denominator\r
+end\r
+\r
+-- Gives the angle between three points.\r
+local function getAngle( x1, y1, x2, y2, x3, y3 )\r
+    local a = getLength( x3, y3, x2, y2 )\r
+    local b = getLength( x1, y1, x2, y2 )\r
+    local c = getLength( x1, y1, x3, y3 )\r
+\r
+   return math.acos( ( a * a + b * b - c * c ) / ( 2 * a * b ) )\r
+end -- }}}\r
+\r
+-- Circle --------------------------------------- {{{\r
+-- Gives the area of the circle.\r
+local function getCircleArea( radius )\r
+       return math.pi * ( radius * radius )\r
+end\r
+\r
+-- Checks if a point is within the radius of a circle.\r
+local function checkCirclePoint( x, y, circleX, circleY, radius )\r
+       return getLength( circleX, circleY, x, y ) <= radius\r
+end\r
+\r
+-- Checks if a point is on a circle.\r
+local function isPointOnCircle( x, y, circleX, circleY, radius )\r
+       return checkFuzzy( getLength( circleX, circleY, x, y ), radius )\r
+end\r
+\r
+-- Gives the circumference of a circle.\r
+local function getCircumference( radius )\r
+       return 2 * math.pi * radius\r
+end\r
+\r
+-- Gives the intersection of a line and a circle.\r
+local function getCircleLineIntersection( circleX, circleY, radius, x1, y1, x2, y2 )\r
+       slope = getSlope( x1, y1, x2, y2 )\r
+       intercept = getYIntercept( x1, y1, slope )\r
+\r
+       if slope then\r
+               local a = ( 1 + slope ^ 2 )\r
+               local b = ( -2 * ( circleX ) + ( 2 * slope * intercept ) - ( 2 * circleY * slope ) )\r
+               local c = ( circleX ^ 2 + intercept ^ 2 - 2 * ( circleY ) * ( intercept ) + circleY ^ 2 - radius ^ 2 )\r
+\r
+               x1, x2 = getQuadraticRoots( a, b, c )\r
+\r
+               if not x1 then return false end\r
+\r
+               y1 = slope * x1 + intercept\r
+               y2 = slope * x2 + intercept\r
+\r
+               if checkFuzzy( x1, x2 ) and checkFuzzy( y1, y2 ) then\r
+                       return 'tangent', x1, y1\r
+               else\r
+                       return 'secant', x1, y1, x2, y2\r
+               end\r
+       else -- Vertical Lines\r
+               local lengthToPoint1 = circleX - x1\r
+               local remainingDistance = lengthToPoint1 - radius\r
+               local intercept = math.sqrt( -( lengthToPoint1 ^ 2 - radius ^ 2 ) )\r
+\r
+               if -( lengthToPoint1 ^ 2 - radius ^ 2 ) < 0 then return false end\r
+\r
+               local bottomX, bottomY = x1, circleY - intercept\r
+               local topX, topY = x1, circleY + intercept\r
+\r
+               if topY ~= bottomY then\r
+                       return 'secant', topX, topY, bottomX, bottomY\r
+               else\r
+                       return 'tangent', topX, topY\r
+               end\r
+       end\r
+end\r
+\r
+-- Gives the type of intersection of a line segment.\r
+local function getCircleSegmentIntersection( circleX, circleY, radius, x1, y1, x2, y2 )\r
+       local Type, x3, y3, x4, y4 = getCircleLineIntersection( circleX, circleY, radius, x1, y1, x2, y2 )\r
+       if not Type then return false end\r
+\r
+       local slope, intercept = getSlope( x1, y1, x2, y2 ), getYIntercept( x1, y1, x2, y2 )\r
+\r
+       if isPointOnCircle( x1, y1, circleX, circleY, radius ) and isPointOnCircle( x2, y2, circleX, circleY, radius ) then -- Both points are on line-segment.\r
+               return 'chord', x1, y1, x2, y2\r
+       end\r
+\r
+       if slope then\r
+               if checkCirclePoint( x1, y1, circleX, circleY, radius ) and checkCirclePoint( x2, y2, circleX, circleY, radius ) then -- Line-segment is fully in circle.\r
+                       return 'enclosed', x1, y1, x2, y2\r
+               elseif x3 and x4 then\r
+                       if checkSegmentPoint( x3, y3, x1, y1, x2, y2 ) and not checkSegmentPoint( x4, y4, x1, y1, x2, y2 ) then -- Only the first of the points is on the line-segment.\r
+                               return 'tangent', x3, y3\r
+                       elseif checkSegmentPoint( x4, y4, x1, y1, x2, y2 ) and not checkSegmentPoint( x3, y3, x1, y1, x2, y2 ) then -- Only the second of the points is on the line-segment.\r
+                               return 'tangent', x4, y4\r
+                       else -- Neither of the points are on the circle (means that the segment is not on the circle, but "encasing" the circle)\r
+                               if checkSegmentPoint( x3, y3, x1, y1, x2, y2 ) and checkSegmentPoint( x4, y4, x1, y1, x2, y2 ) then\r
+                                       return 'secant', x3, y3, x4, y4\r
+                               else\r
+                                       return false\r
+                               end\r
+                       end\r
+               elseif not x4 then -- Is a tangent.\r
+                       if checkSegmentPoint( x3, y3, x1, y1, x2, y2 ) then\r
+                               return 'tangent', x3, y3\r
+                       else -- Neither of the points are on the line-segment (means that the segment is not on the circle or "encasing" the circle).\r
+                               local length = getLength( x1, y1, x2, y2 )\r
+                               local distance1 = getLength( x1, y1, x3, y3 )\r
+                               local distance2 = getLength( x2, y2, x3, y3 )\r
+\r
+                               if length > distance1 or length > distance2 then\r
+                                       return false\r
+                               elseif length < distance1 and length < distance2 then\r
+                                       return false\r
+                               else\r
+                                       return 'tangent', x3, y3\r
+                               end\r
+                       end\r
+               end\r
+       else\r
+               local lengthToPoint1 = circleX - x1\r
+               local remainingDistance = lengthToPoint1 - radius\r
+               local intercept = math.sqrt( -( lengthToPoint1 ^ 2 - radius ^ 2 ) )\r
+\r
+               if -( lengthToPoint1 ^ 2 - radius ^ 2 ) < 0 then return false end\r
+\r
+               local topX, topY = x1, circleY - intercept\r
+               local bottomX, bottomY = x1, circleY + intercept\r
+\r
+               local length = getLength( x1, y1, x2, y2 )\r
+               local distance1 = getLength( x1, y1, topX, topY )\r
+               local distance2 = getLength( x2, y2, topX, topY )\r
+\r
+               if bottomY ~= topY then -- Not a tangent\r
+                       if checkSegmentPoint( topX, topY, x1, y1, x2, y2 ) and checkSegmentPoint( bottomX, bottomY, x1, y1, x2, y2 ) then\r
+                               return 'chord', topX, topY, bottomX, bottomY\r
+                       elseif checkSegmentPoint( topX, topY, x1, y1, x2, y2 ) then\r
+                               return 'tangent', topX, topY\r
+                       elseif checkSegmentPoint( bottomX, bottomY, x1, y1, x2, y2 ) then\r
+                               return 'tangent', bottomX, bottomY\r
+                       else\r
+                               return false\r
+                       end\r
+               else -- Tangent\r
+                       if checkSegmentPoint( topX, topY, x1, y1, x2, y2 ) then\r
+                               return 'tangent', topX, topY\r
+                       else\r
+                               return false\r
+                       end\r
+               end\r
+       end\r
+end\r
+\r
+-- Checks if one circle intersects another circle.\r
+local function getCircleCircleIntersection( circle1x, circle1y, radius1, circle2x, circle2y, radius2 )\r
+       local length = getLength( circle1x, circle1y, circle2x, circle2y )\r
+       if length > radius1 + radius2 then return false end -- If the distance is greater than the two radii, they can't intersect.\r
+       if checkFuzzy( length, 0 ) and checkFuzzy( radius1, radius2 ) then return 'equal' end\r
+       if checkFuzzy( circle1x, circle2x ) and checkFuzzy( circle1y, circle2y ) then return 'collinear' end\r
+\r
+       local a = ( radius1 * radius1 - radius2 * radius2 + length * length ) / ( 2 * length )\r
+       local h = math.sqrt( radius1 * radius1 - a * a )\r
+\r
+       local p2x = circle1x + a * ( circle2x - circle1x ) / length\r
+       local p2y = circle1y + a * ( circle2y - circle1y ) / length\r
+       local p3x = p2x + h * ( circle2y - circle1y ) / length\r
+       local p3y = p2y - h * ( circle2x - circle1x ) / length\r
+       local p4x = p2x - h * ( circle2y - circle1y ) / length\r
+       local p4y = p2y + h * ( circle2x - circle1x ) / length\r
+\r
+       if not validateNumber( p3x ) or not validateNumber( p3y ) or not validateNumber( p4x ) or not validateNumber( p4y ) then\r
+               return 'inside'\r
+       end\r
+\r
+       if checkFuzzy( length, radius1 + radius2 ) or checkFuzzy( length, math.abs( radius1 - radius2 ) ) then return 'tangent', p3x, p3y end\r
+       return 'intersection', p3x, p3y, p4x, p4y\r
+end\r
+\r
+-- Checks if circle1 is entirely inside of circle2.\r
+local function isCircleCompletelyInsideCircle( circle1x, circle1y, circle1radius, circle2x, circle2y, circle2radius )\r
+       if not checkCirclePoint( circle1x, circle1y, circle2x, circle2y, circle2radius ) then return false end\r
+       local Type = getCircleCircleIntersection( circle2x, circle2y, circle2radius, circle1x, circle1y, circle1radius )\r
+       if ( Type ~= 'tangent' and Type ~= 'collinear' and Type ~= 'inside' ) then return false end\r
+       return true\r
+end\r
+\r
+-- Checks if a line-segment is entirely within a circle.\r
+local function isSegmentCompletelyInsideCircle( circleX, circleY, circleRadius, x1, y1, x2, y2 )\r
+       local Type = getCircleSegmentIntersection( circleX, circleY, circleRadius, x1, y1, x2, y2 )\r
+       return Type == 'enclosed'\r
+end -- }}}\r
+\r
+-- Polygon -------------------------------------- {{{\r
+-- Gives the signed area.\r
+-- If the points are clockwise the number is negative, otherwise, it's positive.\r
+local function getSignedPolygonArea( ... )\r
+       local points = checkInput( ... )\r
+\r
+       -- Shoelace formula (https://en.wikipedia.org/wiki/Shoelace_formula).\r
+       points[#points + 1] = points[1]\r
+       points[#points + 1] = points[2]\r
+\r
+       return ( .5 * getSummation( 1, #points / 2,\r
+               function( index )\r
+                       index = index * 2 - 1 -- Convert it to work properly.\r
+                       return ( ( points[index] * cycle( points, index + 3 ) ) - ( cycle( points, index + 2 ) * points[index + 1] ) )\r
+               end\r
+       ) )\r
+end\r
+\r
+-- Simply returns the area of the polygon.\r
+local function getPolygonArea( ... )\r
+       return math.abs( getSignedPolygonArea( ... ) )\r
+end\r
+\r
+-- Gives the height of a triangle, given the base.\r
+-- base, x1, y1, x2, y2, x3, y3, x4, y4\r
+-- base, area\r
+local function getTriangleHeight( base, ... )\r
+       local input = checkInput( ... )\r
+       local area\r
+\r
+       if #input == 1 then area = input[1] -- Given area.\r
+       else area = getPolygonArea( input ) end -- Given coordinates.\r
+\r
+       return ( 2 * area ) / base, area\r
+end\r
+\r
+-- Gives the centroid of the polygon.\r
+local function getCentroid( ... )\r
+       local points = checkInput( ... )\r
+\r
+       points[#points + 1] = points[1]\r
+       points[#points + 1] = points[2]\r
+\r
+       local area = getSignedPolygonArea( points ) -- Needs to be signed here in case points are counter-clockwise.\r
+\r
+       -- This formula: https://en.wikipedia.org/wiki/Centroid#Centroid_of_polygon\r
+       local centroidX = ( 1 / ( 6 * area ) ) * ( getSummation( 1, #points / 2,\r
+               function( index )\r
+                       index = index * 2 - 1 -- Convert it to work properly.\r
+                       return ( ( points[index] + cycle( points, index + 2 ) ) * ( ( points[index] * cycle( points, index + 3 ) ) - ( cycle( points, index + 2 ) * points[index + 1] ) ) )\r
+               end\r
+       ) )\r
+\r
+       local centroidY = ( 1 / ( 6 * area ) ) * ( getSummation( 1, #points / 2,\r
+               function( index )\r
+                       index = index * 2 - 1 -- Convert it to work properly.\r
+                       return ( ( points[index + 1] + cycle( points, index + 3 ) ) * ( ( points[index] * cycle( points, index + 3 ) ) - ( cycle( points, index + 2 ) * points[index + 1] ) ) )\r
+               end\r
+       ) )\r
+\r
+       return centroidX, centroidY\r
+end\r
+\r
+-- Returns whether or not a line intersects a polygon.\r
+-- x1, y1, x2, y2, polygonPoints\r
+local function getPolygonLineIntersection( x1, y1, x2, y2, ... )\r
+       local input = checkInput( ... )\r
+       local choices = {}\r
+\r
+       local slope = getSlope( x1, y1, x2, y2 )\r
+       local intercept = getYIntercept( x1, y1, slope )\r
+\r
+       local x3, y3, x4, y4\r
+       if slope then\r
+               x3, x4 = 1, 2\r
+               y3, y4 = slope * x3 + intercept, slope * x4 + intercept\r
+       else\r
+               x3, x4 = x1, x1\r
+               y3, y4 = y1, y2\r
+       end\r
+\r
+       for i = 1, #input, 2 do\r
+               local x1, y1, x2, y2 = getLineSegmentIntersection( input[i], input[i + 1], cycle( input, i + 2 ), cycle( input, i + 3 ), x3, y3, x4, y4 )\r
+               if x1 and not x2 then choices[#choices + 1] = { x1, y1 }\r
+               elseif x1 and x2 then choices[#choices + 1] = { x1, y1, x2, y2 } end\r
+        -- No need to check 2-point sets since they only intersect each poly line once.\r
+       end\r
+\r
+       local final = removeDuplicatePairs( choices )\r
+       return #final > 0 and final or false\r
+end\r
+\r
+-- Returns if the line segment intersects the polygon.\r
+-- x1, y1, x2, y2, polygonPoints\r
+local function getPolygonSegmentIntersection( x1, y1, x2, y2, ... )\r
+       local input = checkInput( ... )\r
+       local choices = {}\r
+\r
+       for i = 1, #input, 2 do\r
+               local x1, y1, x2, y2 = getSegmentSegmentIntersection( input[i], input[i + 1], cycle( input, i + 2 ), cycle( input, i + 3 ), x1, y1, x2, y2 )\r
+               if x1 and not x2 then choices[#choices + 1] = { x1, y1 }\r
+               elseif x2 then choices[#choices + 1] = { x1, y1, x2, y2 } end\r
+       end\r
+\r
+       local final = removeDuplicatePairs( choices )\r
+       return #final > 0 and final or false\r
+end\r
+\r
+-- Checks if the point lies INSIDE the polygon not on the polygon.\r
+local function checkPolygonPoint( px, py, ... )\r
+       local points = { unpack( checkInput( ... ) ) } -- Make a new table, as to not edit values of previous.\r
+\r
+       local greatest, least = getGreatestPoint( points, 0 )\r
+       if not isWithinBounds( least, py, greatest ) then return false end\r
+       greatest, least = getGreatestPoint( points )\r
+       if not isWithinBounds( least, px, greatest ) then return false end\r
+\r
+       local count = 0\r
+       for i = 1, #points, 2 do\r
+               if checkFuzzy( points[i + 1], py ) then\r
+                       points[i + 1] = py + .001 -- Handles vertices that lie on the point.\r
+            -- Not exactly mathematically correct, but a lot easier.\r
+               end\r
+               if points[i + 3] and checkFuzzy( points[i + 3], py ) then\r
+                       points[i + 3] = py + .001 -- Do not need to worry about alternate case, since points[2] has already been done.\r
+               end\r
+               local x1, y1 = points[i], points[i + 1]\r
+               local x2, y2 = points[i + 2] or points[1], points[i + 3] or points[2]\r
+\r
+               if getSegmentSegmentIntersection( px, py, greatest, py, x1, y1, x2, y2 ) then\r
+                       count = count + 1\r
+               end\r
+       end\r
+\r
+       return count and count % 2 ~= 0\r
+end\r
+\r
+-- Returns if the line segment is fully or partially inside.\r
+-- x1, y1, x2, y2, polygonPoints\r
+local function isSegmentInsidePolygon( x1, y1, x2, y2, ... )\r
+       local input = checkInput( ... )\r
+\r
+       local choices = getPolygonSegmentIntersection( x1, y1, x2, y2, input ) -- If it's partially enclosed that's all we need.\r
+       if choices then return true end\r
+\r
+       if checkPolygonPoint( x1, y1, input ) or checkPolygonPoint( x2, y2, input ) then return true end\r
+       return false\r
+end\r
+\r
+-- Returns whether two polygons intersect.\r
+local function getPolygonPolygonIntersection( polygon1, polygon2 )\r
+       local choices = {}\r
+\r
+       for index1 = 1, #polygon1, 2 do\r
+               local intersections = getPolygonSegmentIntersection( polygon1[index1], polygon1[index1 + 1], cycle( polygon1, index1 + 2 ), cycle( polygon1, index1 + 3 ), polygon2 )\r
+               if intersections then\r
+                       for index2 = 1, #intersections do\r
+                               choices[#choices + 1] = intersections[index2]\r
+                       end\r
+               end\r
+       end\r
+\r
+       for index1 = 1, #polygon2, 2 do\r
+               local intersections = getPolygonSegmentIntersection( polygon2[index1], polygon2[index1 + 1], cycle( polygon2, index1 + 2 ), cycle( polygon2, index1 + 3 ), polygon1 )\r
+               if intersections then\r
+                       for index2 = 1, #intersections do\r
+                               choices[#choices + 1] = intersections[index2]\r
+                       end\r
+               end\r
+       end\r
+\r
+       choices = removeDuplicatePairs( choices )\r
+       for i = #choices, 1, -1 do\r
+               if type( choices[i][1] ) == 'table' then -- Remove co-linear pairs.\r
+                       table.remove( choices, i )\r
+               end\r
+       end\r
+\r
+       return #choices > 0 and choices\r
+end\r
+\r
+-- Returns whether the circle intersects the polygon.\r
+-- x, y, radius, polygonPoints\r
+local function getPolygonCircleIntersection( x, y, radius, ... )\r
+       local input = checkInput( ... )\r
+       local choices = {}\r
+\r
+       for i = 1, #input, 2 do\r
+               local Type, x1, y1, x2, y2 = getCircleSegmentIntersection( x, y, radius, input[i], input[i + 1], cycle( input, i + 2 ), cycle( input, i + 3 ) )\r
+               if x2 then\r
+                       choices[#choices + 1] = { Type, x1, y1, x2, y2 }\r
+               elseif x1 then choices[#choices + 1] = { Type, x1, y1 } end\r
+       end\r
+\r
+       local final = removeDuplicates4Points( choices )\r
+\r
+       return #final > 0 and final\r
+end\r
+\r
+-- Returns whether the circle is inside the polygon.\r
+-- x, y, radius, polygonPoints\r
+local function isCircleInsidePolygon( x, y, radius, ... )\r
+       local input = checkInput( ... )\r
+       return checkPolygonPoint( x, y, input )\r
+end\r
+\r
+-- Returns whether the polygon is inside the polygon.\r
+local function isPolygonInsidePolygon( polygon1, polygon2 )\r
+       local bool = false\r
+       for i = 1, #polygon2, 2 do\r
+               local result = false\r
+               result = isSegmentInsidePolygon( polygon2[i], polygon2[i + 1], cycle( polygon2, i + 2 ), cycle( polygon2, i + 3 ), polygon1 )\r
+               if result then bool = true; break end\r
+       end\r
+       return bool\r
+end\r
+\r
+-- Checks if a segment is completely inside a polygon\r
+local function isSegmentCompletelyInsidePolygon( x1, y1, x2, y2, ... )\r
+       local polygon = checkInput( ... )\r
+       if not checkPolygonPoint( x1, y1, polygon )\r
+       or not checkPolygonPoint( x2, y2, polygon )\r
+       or getPolygonSegmentIntersection( x1, y1, x2, y2, polygon ) then\r
+               return false\r
+       end\r
+       return true\r
+end\r
+\r
+-- Checks if a polygon is completely inside another polygon\r
+local function isPolygonCompletelyInsidePolygon( polygon1, polygon2 )\r
+       for i = 1, #polygon1, 2 do\r
+               local x1, y1 = polygon1[i], polygon1[i + 1]\r
+               local x2, y2 = polygon1[i + 2] or polygon1[1], polygon1[i + 3] or polygon1[2]\r
+               if not isSegmentCompletelyInsidePolygon( x1, y1, x2, y2, polygon2 ) then\r
+                       return false\r
+               end\r
+       end\r
+       return true\r
+end\r
+\r
+-------------- Circle w/ Polygons --------------\r
+-- Gets if a polygon is completely within a circle\r
+-- circleX, circleY, circleRadius, polygonPoints\r
+local function isPolygonCompletelyInsideCircle( circleX, circleY, circleRadius, ... )\r
+       local input = checkInput( ... )\r
+       local function isDistanceLess( px, py, x, y, circleRadius ) -- Faster, does not use math.sqrt\r
+               local distanceX, distanceY = px - x, py - y\r
+               return distanceX * distanceX + distanceY * distanceY < circleRadius * circleRadius -- Faster. For comparing distances only.\r
+       end\r
+\r
+       for i = 1, #input, 2 do\r
+               if not checkCirclePoint( input[i], input[i + 1], circleX, circleY, circleRadius ) then return false end\r
+       end\r
+       return true\r
+end\r
+\r
+-- Checks if a circle is completely within a polygon\r
+-- circleX, circleY, circleRadius, polygonPoints\r
+local function isCircleCompletelyInsidePolygon( circleX, circleY, circleRadius, ... )\r
+       local input = checkInput( ... )\r
+       if not checkPolygonPoint( circleX, circleY, ... ) then return false end\r
+\r
+       local rad2 = circleRadius * circleRadius\r
+\r
+       for i = 1, #input, 2 do\r
+               local x1, y1 = input[i], input[i + 1]\r
+               local x2, y2 = input[i + 2] or input[1], input[i + 3] or input[2]\r
+               if distance2( x1, y1, circleX, circleY ) <= rad2 then return false end\r
+               if getCircleSegmentIntersection( circleX, circleY, circleRadius, x1, y1, x2, y2 ) then return false end\r
+       end\r
+       return true\r
+end -- }}}\r
+\r
+-- Statistics ----------------------------------- {{{\r
+-- Gets the average of a list of points\r
+-- points\r
+local function getMean( ... )\r
+       local input = checkInput( ... )\r
+\r
+       mean = getSummation( 1, #input,\r
+               function( i, t )\r
+                       return input[i]\r
+               end\r
+       ) / #input\r
+\r
+       return mean\r
+end\r
+\r
+local function getMedian( ... )\r
+       local input = checkInput( ... )\r
+\r
+       table.sort( input )\r
+\r
+       local median\r
+       if #input % 2 == 0 then -- If you have an even number of terms, you need to get the average of the middle 2.\r
+               median = getMean( input[#input / 2], input[#input / 2 + 1] )\r
+       else\r
+               median = input[#input / 2 + .5]\r
+       end\r
+\r
+       return median\r
+end\r
+\r
+-- Gets the mode of a number.\r
+local function getMode( ... )\r
+       local input = checkInput( ... )\r
+\r
+       table.sort( input )\r
+       local sorted = {}\r
+       for i = 1, #input do\r
+               local value = input[i]\r
+               sorted[value] = sorted[value] and sorted[value] + 1 or 1\r
+       end\r
+\r
+       local occurrences, least = 0, {}\r
+       for i, value in pairs( sorted ) do\r
+               if value > occurrences then\r
+                       least = { i }\r
+                       occurrences = value\r
+               elseif value == occurrences then\r
+                       least[#least + 1] = i\r
+               end\r
+       end\r
+\r
+       if #least >= 1 then return least, occurrences\r
+       else return false end\r
+end\r
+\r
+-- Gets the range of the numbers.\r
+local function getRange( ... )\r
+       local input = checkInput( ... )\r
+       local high, low = math.max( unpack( input ) ), math.min( unpack( input ) )\r
+       return high - low\r
+end\r
+\r
+-- Gets the variance of a set of numbers.\r
+local function getVariance( ... )\r
+       local input = checkInput( ... )\r
+       local mean = getMean( ... )\r
+       local sum = 0\r
+       for i = 1, #input do\r
+               sum = sum + ( mean - input[i] ) * ( mean - input[i] )\r
+       end\r
+       return sum / #input\r
+end\r
+\r
+-- Gets the standard deviation of a set of numbers.\r
+local function getStandardDeviation( ... )\r
+       return math.sqrt( getVariance( ... ) )\r
+end\r
+\r
+-- Gets the central tendency of a set of numbers.\r
+local function getCentralTendency( ... )\r
+       local mode, occurrences = getMode( ... )\r
+       return mode, occurrences, getMedian( ... ), getMean( ... )\r
+end\r
+\r
+-- Gets the variation ratio of a data set.\r
+local function getVariationRatio( ... )\r
+       local input = checkInput( ... )\r
+       local numbers, times = getMode( ... )\r
+       times = times * #numbers -- Account for bimodal data\r
+       return 1 - ( times / #input )\r
+end\r
+\r
+-- Gets the measures of dispersion of a data set.\r
+local function getDispersion( ... )\r
+       return getVariationRatio( ... ), getRange( ... ), getStandardDeviation( ... )\r
+end -- }}}\r
+\r
+return {\r
+       _VERSION = 'MLib 0.10.0',\r
+       _DESCRIPTION = 'A math and shape-intersection detection library for Lua',\r
+       _URL = 'https://github.com/davisdude/mlib',\r
+    point = {\r
+        rotate = rotatePoint,\r
+        scale = scalePoint,\r
+    },\r
+       line = {\r
+               getLength = getLength,\r
+               getMidpoint = getMidpoint,\r
+               getSlope = getSlope,\r
+               getPerpendicularSlope = getPerpendicularSlope,\r
+               getYIntercept = getYIntercept,\r
+               getIntersection = getLineLineIntersection,\r
+               getClosestPoint = getClosestPoint,\r
+               getSegmentIntersection = getLineSegmentIntersection,\r
+               checkPoint = checkLinePoint,\r
+\r
+               -- Aliases\r
+               getDistance = getLength,\r
+               getCircleIntersection = getCircleLineIntersection,\r
+               getPolygonIntersection = getPolygonLineIntersection,\r
+               getLineIntersection = getLineLineIntersection,\r
+    },\r
+    segment = {\r
+        checkPoint = checkSegmentPoint,\r
+               getPerpendicularBisector = getPerpendicularBisector,\r
+        getIntersection = getSegmentSegmentIntersection,\r
+\r
+        -- Aliases\r
+        getCircleIntersection = getCircleSegmentIntersection,\r
+        getPolygonIntersection = getPolygonSegmentIntersection,\r
+        getLineIntersection = getLineSegmentIntersection,\r
+        getSegmentIntersection = getSegmentSegmentIntersection,\r
+        isSegmentCompletelyInsideCircle = isSegmentCompletelyInsideCircle,\r
+        isSegmentCompletelyInsidePolygon = isSegmentCompletelyInsidePolygon,\r
+       },\r
+       math = {\r
+               getRoot = getRoot,\r
+               isPrime = isPrime,\r
+               round = round,\r
+               getSummation =  getSummation,\r
+               getPercentOfChange = getPercentOfChange,\r
+               getPercentage = getPercentage,\r
+               getQuadraticRoots = getQuadraticRoots,\r
+               getAngle = getAngle,\r
+       },\r
+       circle = {\r
+               getArea = getCircleArea,\r
+               checkPoint = checkCirclePoint,\r
+               isPointOnCircle = isPointOnCircle,\r
+               getCircumference = getCircumference,\r
+               getLineIntersection = getCircleLineIntersection,\r
+               getSegmentIntersection = getCircleSegmentIntersection,\r
+               getCircleIntersection = getCircleCircleIntersection,\r
+               isCircleCompletelyInside = isCircleCompletelyInsideCircle,\r
+               isPolygonCompletelyInside = isPolygonCompletelyInsideCircle,\r
+               isSegmentCompletelyInside = isSegmentCompletelyInsideCircle,\r
+\r
+               -- Aliases\r
+               getPolygonIntersection = getPolygonCircleIntersection,\r
+               isCircleInsidePolygon = isCircleInsidePolygon,\r
+               isCircleCompletelyInsidePolygon = isCircleCompletelyInsidePolygon,\r
+       },\r
+       polygon = {\r
+               getSignedArea = getSignedPolygonArea,\r
+               getArea = getPolygonArea,\r
+               getTriangleHeight = getTriangleHeight,\r
+               getCentroid = getCentroid,\r
+               getLineIntersection = getPolygonLineIntersection,\r
+               getSegmentIntersection = getPolygonSegmentIntersection,\r
+               checkPoint = checkPolygonPoint,\r
+               isSegmentInside = isSegmentInsidePolygon,\r
+               getPolygonIntersection = getPolygonPolygonIntersection,\r
+               getCircleIntersection = getPolygonCircleIntersection,\r
+               isCircleInside = isCircleInsidePolygon,\r
+               isPolygonInside = isPolygonInsidePolygon,\r
+               isCircleCompletelyInside = isCircleCompletelyInsidePolygon,\r
+               isSegmentCompletelyInside = isSegmentCompletelyInsidePolygon,\r
+               isPolygonCompletelyInside = isPolygonCompletelyInsidePolygon,\r
+\r
+               -- Aliases\r
+               isCircleCompletelyOver = isPolygonCompletelyInsideCircle,\r
+       },\r
+       statistics = {\r
+               getMean = getMean,\r
+               getMedian = getMedian,\r
+               getMode = getMode,\r
+               getRange = getRange,\r
+               getVariance = getVariance,\r
+               getStandardDeviation = getStandardDeviation,\r
+               getCentralTendency = getCentralTendency,\r
+               getVariationRatio = getVariationRatio,\r
+               getDispersion = getDispersion,\r
+       },\r
+}\r