-- 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')
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'))
end
function love.update(dt)
+ lovebird.update()
+
active_map:update(dt)
+ world:update(dt)
end
function love.keypressed(key)
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
--- /dev/null
+Subproject commit a83eb64db2db55e85205f15013eb6e7327be605d
--- /dev/null
+--
+-- 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 ●");
+ 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 ○");
+ /* 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 = {
+ ["<"] = "<",
+ ["&"] = "&",
+ ['"'] = """,
+ ["'"] = "'",
+}
+
+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
--- /dev/null
+--[[\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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
+\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
--- /dev/null
+--[[ 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