aboutsummaryrefslogtreecommitdiff
path: root/libs/windfield
diff options
context:
space:
mode:
Diffstat (limited to 'libs/windfield')
-rw-r--r--libs/windfield/init.lua929
-rw-r--r--libs/windfield/mlib/Changes.txt568
-rw-r--r--libs/windfield/mlib/LICENSE.md17
-rw-r--r--libs/windfield/mlib/README.md890
-rw-r--r--libs/windfield/mlib/mlib.lua1152
5 files changed, 3556 insertions, 0 deletions
diff --git a/libs/windfield/init.lua b/libs/windfield/init.lua
new file mode 100644
index 0000000..2c7192a
--- /dev/null
+++ b/libs/windfield/init.lua
@@ -0,0 +1,929 @@
+--[[
+The MIT License (MIT)
+
+Copyright (c) 2018 SSYGEN
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+]]--
+
+local path = ... .. '.'
+local wf = {}
+wf.Math = require(path .. 'mlib.mlib')
+
+World = {}
+World.__index = World
+
+function wf.newWorld(xg, yg, sleep)
+ local world = wf.World.new(wf, xg, yg, sleep)
+
+ world.box2d_world:setCallbacks(world.collisionOnEnter, world.collisionOnExit, world.collisionPre, world.collisionPost)
+ world:collisionClear()
+ world:addCollisionClass('Default')
+
+ -- Points all box2d_world functions to this wf.World object
+ -- This means that the user can call world:setGravity for instance without having to say world.box2d_world:setGravity
+ for k, v in pairs(world.box2d_world.__index) do
+ 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
+ world[k] = function(self, ...)
+ return v(self.box2d_world, ...)
+ end
+ end
+ end
+
+ return world
+end
+
+function World.new(wf, xg, yg, sleep)
+ local self = {}
+ local settings = settings or {}
+ self.wf = wf
+
+ self.draw_query_for_n_frames = 10
+ self.query_debug_drawing_enabled = false
+ self.explicit_collision_events = false
+ self.collision_classes = {}
+ self.masks = {}
+ self.is_sensor_memo = {}
+ self.query_debug_draw = {}
+
+ love.physics.setMeter(32)
+ self.box2d_world = love.physics.newWorld(xg, yg, sleep)
+
+ return setmetatable(self, World)
+end
+
+function World:update(dt)
+ self:collisionEventsClear()
+ self.box2d_world:update(dt)
+end
+
+function World:draw(alpha)
+ -- get the current color values to reapply
+ local r, g, b, a = love.graphics.getColor()
+ -- alpha value is optional
+ alpha = alpha or 255
+ -- Colliders debug
+ love.graphics.setColor(222, 222, 222, alpha)
+ local bodies = self.box2d_world:getBodies()
+ for _, body in ipairs(bodies) do
+ local fixtures = body:getFixtures()
+ for _, fixture in ipairs(fixtures) do
+ if fixture:getShape():type() == 'PolygonShape' then
+ love.graphics.polygon('line', body:getWorldPoints(fixture:getShape():getPoints()))
+ elseif fixture:getShape():type() == 'EdgeShape' or fixture:getShape():type() == 'ChainShape' then
+ local points = {body:getWorldPoints(fixture:getShape():getPoints())}
+ for i = 1, #points, 2 do
+ if i < #points-2 then love.graphics.line(points[i], points[i+1], points[i+2], points[i+3]) end
+ end
+ elseif fixture:getShape():type() == 'CircleShape' then
+ local body_x, body_y = body:getPosition()
+ local shape_x, shape_y = fixture:getShape():getPoint()
+ local r = fixture:getShape():getRadius()
+ love.graphics.circle('line', body_x + shape_x, body_y + shape_y, r, 360)
+ end
+ end
+ end
+ love.graphics.setColor(255, 255, 255, alpha)
+
+ -- Joint debug
+ love.graphics.setColor(222, 128, 64, alpha)
+ local joints = self.box2d_world:getJoints()
+ for _, joint in ipairs(joints) do
+ local x1, y1, x2, y2 = joint:getAnchors()
+ if x1 and y1 then love.graphics.circle('line', x1, y1, 4) end
+ if x2 and y2 then love.graphics.circle('line', x2, y2, 4) end
+ end
+ love.graphics.setColor(255, 255, 255, alpha)
+
+ -- Query debug
+ love.graphics.setColor(64, 64, 222, alpha)
+ for _, query_draw in ipairs(self.query_debug_draw) do
+ query_draw.frames = query_draw.frames - 1
+ if query_draw.type == 'circle' then
+ love.graphics.circle('line', query_draw.x, query_draw.y, query_draw.r)
+ elseif query_draw.type == 'rectangle' then
+ love.graphics.rectangle('line', query_draw.x, query_draw.y, query_draw.w, query_draw.h)
+ elseif query_draw.type == 'line' then
+ love.graphics.line(query_draw.x1, query_draw.y1, query_draw.x2, query_draw.y2)
+ elseif query_draw.type == 'polygon' then
+ local triangles = love.math.triangulate(query_draw.vertices)
+ for _, triangle in ipairs(triangles) do love.graphics.polygon('line', triangle) end
+ end
+ end
+ for i = #self.query_debug_draw, 1, -1 do
+ if self.query_debug_draw[i].frames <= 0 then
+ table.remove(self.query_debug_draw, i)
+ end
+ end
+ love.graphics.setColor(r, g, b, a)
+end
+
+function World:setQueryDebugDrawing(value)
+ self.query_debug_drawing_enabled = value
+end
+
+function World:setExplicitCollisionEvents(value)
+ self.explicit_collision_events = value
+end
+
+function World:addCollisionClass(collision_class_name, collision_class)
+ if self.collision_classes[collision_class_name] then error('Collision class ' .. collision_class_name .. ' already exists.') end
+
+ if self.explicit_collision_events then
+ self.collision_classes[collision_class_name] = collision_class or {}
+ else
+ self.collision_classes[collision_class_name] = collision_class or {}
+ self.collision_classes[collision_class_name].enter = {}
+ self.collision_classes[collision_class_name].exit = {}
+ self.collision_classes[collision_class_name].pre = {}
+ self.collision_classes[collision_class_name].post = {}
+ for c_class_name, _ in pairs(self.collision_classes) do
+ table.insert(self.collision_classes[collision_class_name].enter, c_class_name)
+ table.insert(self.collision_classes[collision_class_name].exit, c_class_name)
+ table.insert(self.collision_classes[collision_class_name].pre, c_class_name)
+ table.insert(self.collision_classes[collision_class_name].post, c_class_name)
+ end
+ for c_class_name, _ in pairs(self.collision_classes) do
+ table.insert(self.collision_classes[c_class_name].enter, collision_class_name)
+ table.insert(self.collision_classes[c_class_name].exit, collision_class_name)
+ table.insert(self.collision_classes[c_class_name].pre, collision_class_name)
+ table.insert(self.collision_classes[c_class_name].post, collision_class_name)
+ end
+ end
+
+ self:collisionClassesSet()
+end
+
+function World:collisionClassesSet()
+ self:generateCategoriesMasks()
+
+ self:collisionClear()
+ local collision_table = self:getCollisionCallbacksTable()
+ for collision_class_name, collision_list in pairs(collision_table) do
+ for _, collision_info in ipairs(collision_list) do
+ if collision_info.type == 'enter' then self:addCollisionEnter(collision_class_name, collision_info.other) end
+ if collision_info.type == 'exit' then self:addCollisionExit(collision_class_name, collision_info.other) end
+ if collision_info.type == 'pre' then self:addCollisionPre(collision_class_name, collision_info.other) end
+ if collision_info.type == 'post' then self:addCollisionPost(collision_class_name, collision_info.other) end
+ end
+ end
+
+ self:collisionEventsClear()
+end
+
+function World:collisionClear()
+ self.collisions = {}
+ self.collisions.on_enter = {}
+ self.collisions.on_enter.sensor = {}
+ self.collisions.on_enter.non_sensor = {}
+ self.collisions.on_exit = {}
+ self.collisions.on_exit.sensor = {}
+ self.collisions.on_exit.non_sensor = {}
+ self.collisions.pre = {}
+ self.collisions.pre.sensor = {}
+ self.collisions.pre.non_sensor = {}
+ self.collisions.post = {}
+ self.collisions.post.sensor = {}
+ self.collisions.post.non_sensor = {}
+end
+
+function World:collisionEventsClear()
+ local bodies = self.box2d_world:getBodies()
+ for _, body in ipairs(bodies) do
+ local collider = body:getFixtures()[1]:getUserData()
+ collider:collisionEventsClear()
+ end
+end
+
+function World:addCollisionEnter(type1, type2)
+ if not self:isCollisionBetweenSensors(type1, type2) then
+ table.insert(self.collisions.on_enter.non_sensor, {type1 = type1, type2 = type2})
+ else table.insert(self.collisions.on_enter.sensor, {type1 = type1, type2 = type2}) end
+end
+
+function World:addCollisionExit(type1, type2)
+ if not self:isCollisionBetweenSensors(type1, type2) then
+ table.insert(self.collisions.on_exit.non_sensor, {type1 = type1, type2 = type2})
+ else table.insert(self.collisions.on_exit.sensor, {type1 = type1, type2 = type2}) end
+end
+
+function World:addCollisionPre(type1, type2)
+ if not self:isCollisionBetweenSensors(type1, type2) then
+ table.insert(self.collisions.pre.non_sensor, {type1 = type1, type2 = type2})
+ else table.insert(self.collisions.pre.sensor, {type1 = type1, type2 = type2}) end
+end
+
+function World:addCollisionPost(type1, type2)
+ if not self:isCollisionBetweenSensors(type1, type2) then
+ table.insert(self.collisions.post.non_sensor, {type1 = type1, type2 = type2})
+ else table.insert(self.collisions.post.sensor, {type1 = type1, type2 = type2}) end
+end
+
+function World:doesType1IgnoreType2(type1, type2)
+ local collision_ignores = {}
+ for collision_class_name, collision_class in pairs(self.collision_classes) do
+ collision_ignores[collision_class_name] = collision_class.ignores or {}
+ end
+ local all = {}
+ for collision_class_name, _ in pairs(collision_ignores) do
+ table.insert(all, collision_class_name)
+ end
+ local ignored_types = {}
+ for _, collision_class_type in ipairs(collision_ignores[type1]) do
+ if collision_class_type == 'All' then
+ for _, collision_class_name in ipairs(all) do
+ table.insert(ignored_types, collision_class_name)
+ end
+ else table.insert(ignored_types, collision_class_type) end
+ end
+ for key, _ in pairs(collision_ignores[type1]) do
+ if key == 'except' then
+ for _, except_type in ipairs(collision_ignores[type1].except) do
+ for i = #ignored_types, 1, -1 do
+ if ignored_types[i] == except_type then table.remove(ignored_types, i) end
+ end
+ end
+ end
+ end
+ for _, ignored_type in ipairs(ignored_types) do
+ if ignored_type == type2 then return true end
+ end
+end
+
+function World:isCollisionBetweenSensors(type1, type2)
+ if not self.is_sensor_memo[type1] then self.is_sensor_memo[type1] = {} end
+ 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
+ if self.is_sensor_memo[type1][type2] then return true
+ else return false end
+end
+
+-- https://love2d.org/forums/viewtopic.php?f=4&t=75441
+function World:generateCategoriesMasks()
+ local collision_ignores = {}
+ for collision_class_name, collision_class in pairs(self.collision_classes) do
+ collision_ignores[collision_class_name] = collision_class.ignores or {}
+ end
+ local incoming = {}
+ local expanded = {}
+ local all = {}
+ for object_type, _ in pairs(collision_ignores) do
+ incoming[object_type] = {}
+ expanded[object_type] = {}
+ table.insert(all, object_type)
+ end
+ for object_type, ignore_list in pairs(collision_ignores) do
+ for key, ignored_type in pairs(ignore_list) do
+ if ignored_type == 'All' then
+ for _, all_object_type in ipairs(all) do
+ table.insert(incoming[all_object_type], object_type)
+ table.insert(expanded[object_type], all_object_type)
+ end
+ elseif type(ignored_type) == 'string' then
+ if ignored_type ~= 'All' then
+ table.insert(incoming[ignored_type], object_type)
+ table.insert(expanded[object_type], ignored_type)
+ end
+ end
+ if key == 'except' then
+ for _, except_ignored_type in ipairs(ignored_type) do
+ for i, v in ipairs(incoming[except_ignored_type]) do
+ if v == object_type then
+ table.remove(incoming[except_ignored_type], i)
+ break
+ end
+ end
+ end
+ for _, except_ignored_type in ipairs(ignored_type) do
+ for i, v in ipairs(expanded[object_type]) do
+ if v == except_ignored_type then
+ table.remove(expanded[object_type], i)
+ break
+ end
+ end
+ end
+ end
+ end
+ end
+ local edge_groups = {}
+ for k, v in pairs(incoming) do
+ table.sort(v, function(a, b) return string.lower(a) < string.lower(b) end)
+ end
+ local i = 0
+ for k, v in pairs(incoming) do
+ local str = ""
+ for _, c in ipairs(v) do
+ str = str .. c
+ end
+ if not edge_groups[str] then i = i + 1; edge_groups[str] = {n = i} end
+ table.insert(edge_groups[str], k)
+ end
+ local categories = {}
+ for k, _ in pairs(collision_ignores) do
+ categories[k] = {}
+ end
+ for k, v in pairs(edge_groups) do
+ for i, c in ipairs(v) do
+ categories[c] = v.n
+ end
+ end
+ for k, v in pairs(expanded) do
+ local category = {categories[k]}
+ local current_masks = {}
+ for _, c in ipairs(v) do
+ table.insert(current_masks, categories[c])
+ end
+ self.masks[k] = {categories = category, masks = current_masks}
+ end
+end
+
+function World:getCollisionCallbacksTable()
+ local collision_table = {}
+ for collision_class_name, collision_class in pairs(self.collision_classes) do
+ collision_table[collision_class_name] = {}
+ for _, v in ipairs(collision_class.enter or {}) do table.insert(collision_table[collision_class_name], {type = 'enter', other = v}) end
+ for _, v in ipairs(collision_class.exit or {}) do table.insert(collision_table[collision_class_name], {type = 'exit', other = v}) end
+ for _, v in ipairs(collision_class.pre or {}) do table.insert(collision_table[collision_class_name], {type = 'pre', other = v}) end
+ for _, v in ipairs(collision_class.post or {}) do table.insert(collision_table[collision_class_name], {type = 'post', other = v}) end
+ end
+ return collision_table
+end
+
+local function collEnsure(collision_class_name1, a, collision_class_name2, b)
+ if a.collision_class == collision_class_name2 and b.collision_class == collision_class_name1 then return b, a
+ else return a, b end
+end
+
+local function collIf(collision_class_name1, collision_class_name2, a, b)
+ if (a.collision_class == collision_class_name1 and b.collision_class == collision_class_name2) or
+ (a.collision_class == collision_class_name2 and b.collision_class == collision_class_name1) then
+ return true
+ else return false end
+end
+
+function World.collisionOnEnter(fixture_a, fixture_b, contact)
+ local a, b = fixture_a:getUserData(), fixture_b:getUserData()
+
+ if fixture_a:isSensor() and fixture_b:isSensor() then
+ if a and b then
+ for _, collision in ipairs(a.world.collisions.on_enter.sensor) do
+ if collIf(collision.type1, collision.type2, a, b) then
+ a, b = collEnsure(collision.type1, a, collision.type2, b)
+ table.insert(a.collision_events[collision.type2], {collision_type = 'enter', collider_1 = a, collider_2 = b, contact = contact})
+ if collision.type1 == collision.type2 then
+ table.insert(b.collision_events[collision.type1], {collision_type = 'enter', collider_1 = b, collider_2 = a, contact = contact})
+ end
+ end
+ end
+ end
+
+ elseif not (fixture_a:isSensor() or fixture_b:isSensor()) then
+ if a and b then
+ for _, collision in ipairs(a.world.collisions.on_enter.non_sensor) do
+ if collIf(collision.type1, collision.type2, a, b) then
+ a, b = collEnsure(collision.type1, a, collision.type2, b)
+ table.insert(a.collision_events[collision.type2], {collision_type = 'enter', collider_1 = a, collider_2 = b, contact = contact})
+ if collision.type1 == collision.type2 then
+ table.insert(b.collision_events[collision.type1], {collision_type = 'enter', collider_1 = b, collider_2 = a, contact = contact})
+ end
+ end
+ end
+ end
+ end
+end
+
+function World.collisionOnExit(fixture_a, fixture_b, contact)
+ local a, b = fixture_a:getUserData(), fixture_b:getUserData()
+
+ if fixture_a:isSensor() and fixture_b:isSensor() then
+ if a and b then
+ for _, collision in ipairs(a.world.collisions.on_exit.sensor) do
+ if collIf(collision.type1, collision.type2, a, b) then
+ a, b = collEnsure(collision.type1, a, collision.type2, b)
+ table.insert(a.collision_events[collision.type2], {collision_type = 'exit', collider_1 = a, collider_2 = b, contact = contact})
+ if collision.type1 == collision.type2 then
+ table.insert(b.collision_events[collision.type1], {collision_type = 'exit', collider_1 = b, collider_2 = a, contact = contact})
+ end
+ end
+ end
+ end
+
+ elseif not (fixture_a:isSensor() or fixture_b:isSensor()) then
+ if a and b then
+ for _, collision in ipairs(a.world.collisions.on_exit.non_sensor) do
+ if collIf(collision.type1, collision.type2, a, b) then
+ a, b = collEnsure(collision.type1, a, collision.type2, b)
+ table.insert(a.collision_events[collision.type2], {collision_type = 'exit', collider_1 = a, collider_2 = b, contact = contact})
+ if collision.type1 == collision.type2 then
+ table.insert(b.collision_events[collision.type1], {collision_type = 'exit', collider_1 = b, collider_2 = a, contact = contact})
+ end
+ end
+ end
+ end
+ end
+end
+
+function World.collisionPre(fixture_a, fixture_b, contact)
+ local a, b = fixture_a:getUserData(), fixture_b:getUserData()
+
+ if fixture_a:isSensor() and fixture_b:isSensor() then
+ if a and b then
+ for _, collision in ipairs(a.world.collisions.pre.sensor) do
+ if collIf(collision.type1, collision.type2, a, b) then
+ a, b = collEnsure(collision.type1, a, collision.type2, b)
+ a:preSolve(b, contact)
+ if collision.type1 == collision.type2 then
+ b:preSolve(a, contact)
+ end
+ end
+ end
+ end
+
+ elseif not (fixture_a:isSensor() or fixture_b:isSensor()) then
+ if a and b then
+ for _, collision in ipairs(a.world.collisions.pre.non_sensor) do
+ if collIf(collision.type1, collision.type2, a, b) then
+ a, b = collEnsure(collision.type1, a, collision.type2, b)
+ a:preSolve(b, contact)
+ if collision.type1 == collision.type2 then
+ b:preSolve(a, contact)
+ end
+ end
+ end
+ end
+ end
+end
+
+function World.collisionPost(fixture_a, fixture_b, contact, ni1, ti1, ni2, ti2)
+ local a, b = fixture_a:getUserData(), fixture_b:getUserData()
+
+ if fixture_a:isSensor() and fixture_b:isSensor() then
+ if a and b then
+ for _, collision in ipairs(a.world.collisions.post.sensor) do
+ if collIf(collision.type1, collision.type2, a, b) then
+ a, b = collEnsure(collision.type1, a, collision.type2, b)
+ a:postSolve(b, contact, ni1, ti1, ni2, ti2)
+ if collision.type1 == collision.type2 then
+ b:postSolve(a, contact, ni1, ti1, ni2, ti2)
+ end
+ end
+ end
+ end
+
+ elseif not (fixture_a:isSensor() or fixture_b:isSensor()) then
+ if a and b then
+ for _, collision in ipairs(a.world.collisions.post.non_sensor) do
+ if collIf(collision.type1, collision.type2, a, b) then
+ a, b = collEnsure(collision.type1, a, collision.type2, b)
+ a:postSolve(b, contact, ni1, ti1, ni2, ti2)
+ if collision.type1 == collision.type2 then
+ b:postSolve(a, contact, ni1, ti1, ni2, ti2)
+ end
+ end
+ end
+ end
+ end
+end
+
+function World:newCircleCollider(x, y, r, settings)
+ return self.wf.Collider.new(self, 'Circle', x, y, r, settings)
+end
+
+function World:newRectangleCollider(x, y, w, h, settings)
+ return self.wf.Collider.new(self, 'Rectangle', x, y, w, h, settings)
+end
+
+function World:newBSGRectangleCollider(x, y, w, h, corner_cut_size, settings)
+ return self.wf.Collider.new(self, 'BSGRectangle', x, y, w, h, corner_cut_size, settings)
+end
+
+function World:newPolygonCollider(vertices, settings)
+ return self.wf.Collider.new(self, 'Polygon', vertices, settings)
+end
+
+function World:newLineCollider(x1, y1, x2, y2, settings)
+ return self.wf.Collider.new(self, 'Line', x1, y1, x2, y2, settings)
+end
+
+function World:newChainCollider(vertices, loop, settings)
+ return self.wf.Collider.new(self, 'Chain', vertices, loop, settings)
+end
+
+-- Internal AABB box2d query used before going for more specific and precise computations.
+function World:_queryBoundingBox(x1, y1, x2, y2)
+ local colliders = {}
+ local callback = function(fixture)
+ if not fixture:isSensor() then table.insert(colliders, fixture:getUserData()) end
+ return true
+ end
+ self.box2d_world:queryBoundingBox(x1, y1, x2, y2, callback)
+ return colliders
+end
+
+function World:collisionClassInCollisionClassesList(collision_class, collision_classes)
+ if collision_classes[1] == 'All' then
+ local all_collision_classes = {}
+ for class, _ in pairs(self.collision_classes) do
+ table.insert(all_collision_classes, class)
+ end
+ if collision_classes.except then
+ for _, except in ipairs(collision_classes.except) do
+ for i, class in ipairs(all_collision_classes) do
+ if class == except then
+ table.remove(all_collision_classes, i)
+ break
+ end
+ end
+ end
+ end
+ for _, class in ipairs(all_collision_classes) do
+ if class == collision_class then return true end
+ end
+ else
+ for _, class in ipairs(collision_classes) do
+ if class == collision_class then return true end
+ end
+ end
+end
+
+function World:queryCircleArea(x, y, radius, collision_class_names)
+ if not collision_class_names then collision_class_names = {'All'} end
+ 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
+
+ local colliders = self:_queryBoundingBox(x-radius, y-radius, x+radius, y+radius)
+ local outs = {}
+ for _, collider in ipairs(colliders) do
+ if self:collisionClassInCollisionClassesList(collider.collision_class, collision_class_names) then
+ for _, fixture in ipairs(collider.body:getFixtures()) do
+ if self.wf.Math.polygon.getCircleIntersection(x, y, radius, {collider.body:getWorldPoints(fixture:getShape():getPoints())}) then
+ table.insert(outs, collider)
+ break
+ end
+ end
+ end
+ end
+ return outs
+end
+
+function World:queryRectangleArea(x, y, w, h, collision_class_names)
+ if not collision_class_names then collision_class_names = {'All'} end
+ 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
+
+ local colliders = self:_queryBoundingBox(x, y, x+w, y+h)
+ local outs = {}
+ for _, collider in ipairs(colliders) do
+ if self:collisionClassInCollisionClassesList(collider.collision_class, collision_class_names) then
+ for _, fixture in ipairs(collider.body:getFixtures()) do
+ 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
+ table.insert(outs, collider)
+ break
+ end
+ end
+ end
+ end
+ return outs
+end
+
+function World:queryPolygonArea(vertices, collision_class_names)
+ if not collision_class_names then collision_class_names = {'All'} end
+ 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
+
+ local cx, cy = self.wf.Math.polygon.getCentroid(vertices)
+ local d_max = 0
+ for i = 1, #vertices, 2 do
+ local d = self.wf.Math.line.getLength(cx, cy, vertices[i], vertices[i+1])
+ if d > d_max then d_max = d end
+ end
+ local colliders = self:_queryBoundingBox(cx-d_max, cy-d_max, cx+d_max, cy+d_max)
+ local outs = {}
+ for _, collider in ipairs(colliders) do
+ if self:collisionClassInCollisionClassesList(collider.collision_class, collision_class_names) then
+ for _, fixture in ipairs(collider.body:getFixtures()) do
+ if self.wf.Math.polygon.isPolygonInside(vertices, {collider.body:getWorldPoints(fixture:getShape():getPoints())}) then
+ table.insert(outs, collider)
+ break
+ end
+ end
+ end
+ end
+ return outs
+end
+
+function World:queryLine(x1, y1, x2, y2, collision_class_names)
+ if not collision_class_names then collision_class_names = {'All'} end
+ if self.query_debug_drawing_enabled then
+ table.insert(self.query_debug_draw, {type = 'line', x1 = x1, y1 = y1, x2 = x2, y2 = y2, frames = self.draw_query_for_n_frames})
+ end
+
+ local colliders = {}
+ local callback = function(fixture, ...)
+ if not fixture:isSensor() then table.insert(colliders, fixture:getUserData()) end
+ return 1
+ end
+ self.box2d_world:rayCast(x1, y1, x2, y2, callback)
+
+ local outs = {}
+ for _, collider in ipairs(colliders) do
+ if self:collisionClassInCollisionClassesList(collider.collision_class, collision_class_names) then
+ table.insert(outs, collider)
+ end
+ end
+ return outs
+end
+
+function World:addJoint(joint_type, ...)
+ local args = {...}
+ if args[1].body then args[1] = args[1].body end
+ if type(args[2]) == "table" and args[2].body then args[2] = args[2].body end
+ local joint = love.physics['new' .. joint_type](unpack(args))
+ return joint
+end
+
+function World:removeJoint(joint)
+ joint:destroy()
+end
+
+function World:destroy()
+ local bodies = self.box2d_world:getBodies()
+ for _, body in ipairs(bodies) do
+ local collider = body:getFixtures()[1]:getUserData()
+ collider:destroy()
+ end
+ local joints = self.box2d_world:getJoints()
+ for _, joint in ipairs(joints) do joint:destroy() end
+ self.box2d_world:destroy()
+ self.box2d_world = nil
+end
+
+
+
+local Collider = {}
+Collider.__index = Collider
+
+local generator = love.math.newRandomGenerator(os.time())
+local function UUID()
+ local fn = function(x)
+ local r = generator:random(16) - 1
+ r = (x == "x") and (r + 1) or (r % 4) + 9
+ return ("0123456789abcdef"):sub(r, r)
+ end
+ return (("xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx"):gsub("[xy]", fn))
+end
+
+function Collider.new(world, collider_type, ...)
+ local self = {}
+ self.id = UUID()
+ self.world = world
+ self.type = collider_type
+ self.object = nil
+
+ self.shapes = {}
+ self.fixtures = {}
+ self.sensors = {}
+
+ self.collision_events = {}
+ self.collision_stay = {}
+ self.enter_collision_data = {}
+ self.exit_collision_data = {}
+ self.stay_collision_data = {}
+
+ local args = {...}
+ local shape, fixture
+ if self.type == 'Circle' then
+ self.collision_class = (args[4] and args[4].collision_class) or 'Default'
+ self.body = love.physics.newBody(self.world.box2d_world, args[1], args[2], (args[4] and args[4].body_type) or 'dynamic')
+ shape = love.physics.newCircleShape(args[3])
+
+ elseif self.type == 'Rectangle' then
+ self.collision_class = (args[5] and args[5].collision_class) or 'Default'
+ 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')
+ shape = love.physics.newRectangleShape(args[3], args[4])
+
+ elseif self.type == 'BSGRectangle' then
+ self.collision_class = (args[6] and args[6].collision_class) or 'Default'
+ 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')
+ local w, h, s = args[3], args[4], args[5]
+ shape = love.physics.newPolygonShape({
+ -w/2, -h/2 + s, -w/2 + s, -h/2,
+ w/2 - s, -h/2, w/2, -h/2 + s,
+ w/2, h/2 - s, w/2 - s, h/2,
+ -w/2 + s, h/2, -w/2, h/2 - s
+ })
+
+ elseif self.type == 'Polygon' then
+ self.collision_class = (args[2] and args[2].collision_class) or 'Default'
+ self.body = love.physics.newBody(self.world.box2d_world, 0, 0, (args[2] and args[2].body_type) or 'dynamic')
+ shape = love.physics.newPolygonShape(unpack(args[1]))
+
+ elseif self.type == 'Line' then
+ self.collision_class = (args[5] and args[5].collision_class) or 'Default'
+ self.body = love.physics.newBody(self.world.box2d_world, 0, 0, (args[5] and args[5].body_type) or 'dynamic')
+ shape = love.physics.newEdgeShape(args[1], args[2], args[3], args[4])
+
+ elseif self.type == 'Chain' then
+ self.collision_class = (args[3] and args[3].collision_class) or 'Default'
+ self.body = love.physics.newBody(self.world.box2d_world, 0, 0, (args[3] and args[3].body_type) or 'dynamic')
+ shape = love.physics.newChainShape(args[1], unpack(args[2]))
+ end
+
+ -- Define collision classes and attach them to fixture and sensor
+ fixture = love.physics.newFixture(self.body, shape)
+ if self.world.masks[self.collision_class] then
+ fixture:setCategory(unpack(self.world.masks[self.collision_class].categories))
+ fixture:setMask(unpack(self.world.masks[self.collision_class].masks))
+ end
+ fixture:setUserData(self)
+ local sensor = love.physics.newFixture(self.body, shape)
+ sensor:setSensor(true)
+ sensor:setUserData(self)
+
+ self.shapes['main'] = shape
+ self.fixtures['main'] = fixture
+ self.sensors['main'] = sensor
+ self.shape = shape
+ self.fixture = fixture
+
+ self.preSolve = function() end
+ self.postSolve = function() end
+
+ -- Points all body, fixture and shape functions to this wf.Collider object
+ -- This means that the user can call collider:setLinearVelocity for instance without having to say collider.body:setLinearVelocity
+ for k, v in pairs(self.body.__index) do
+ if k ~= '__gc' and k ~= '__eq' and k ~= '__index' and k ~= '__tostring' and k ~= 'destroy' and k ~= 'type' and k ~= 'typeOf' then
+ self[k] = function(self, ...)
+ return v(self.body, ...)
+ end
+ end
+ end
+ for k, v in pairs(self.fixture.__index) do
+ if k ~= '__gc' and k ~= '__eq' and k ~= '__index' and k ~= '__tostring' and k ~= 'destroy' and k ~= 'type' and k ~= 'typeOf' then
+ self[k] = function(self, ...)
+ return v(self.fixture, ...)
+ end
+ end
+ end
+ for k, v in pairs(self.shape.__index) do
+ if k ~= '__gc' and k ~= '__eq' and k ~= '__index' and k ~= '__tostring' and k ~= 'destroy' and k ~= 'type' and k ~= 'typeOf' then
+ self[k] = function(self, ...)
+ return v(self.shape, ...)
+ end
+ end
+ end
+
+ return setmetatable(self, Collider)
+end
+
+function Collider:collisionEventsClear()
+ self.collision_events = {}
+ for other, _ in pairs(self.world.collision_classes) do
+ self.collision_events[other] = {}
+ end
+end
+
+function Collider:setCollisionClass(collision_class_name)
+ if not self.world.collision_classes[collision_class_name] then error("Collision class " .. collision_class_name .. " doesn't exist.") end
+ self.collision_class = collision_class_name
+ for _, fixture in pairs(self.fixtures) do
+ if self.world.masks[collision_class_name] then
+ fixture:setCategory(unpack(self.world.masks[collision_class_name].categories))
+ fixture:setMask(unpack(self.world.masks[collision_class_name].masks))
+ end
+ end
+end
+
+function Collider:enter(other_collision_class_name)
+ local events = self.collision_events[other_collision_class_name]
+ if events and #events >= 1 then
+ for _, e in ipairs(events) do
+ if e.collision_type == 'enter' then
+ if not self.collision_stay[other_collision_class_name] then self.collision_stay[other_collision_class_name] = {} end
+ table.insert(self.collision_stay[other_collision_class_name], {collider = e.collider_2, contact = e.contact})
+ self.enter_collision_data[other_collision_class_name] = {collider = e.collider_2, contact = e.contact}
+ return true
+ end
+ end
+ end
+end
+
+function Collider:getEnterCollisionData(other_collision_class_name)
+ return self.enter_collision_data[other_collision_class_name]
+end
+
+function Collider:exit(other_collision_class_name)
+ local events = self.collision_events[other_collision_class_name]
+ if events and #events >= 1 then
+ for _, e in ipairs(events) do
+ if e.collision_type == 'exit' then
+ if self.collision_stay[other_collision_class_name] then
+ for i = #self.collision_stay[other_collision_class_name], 1, -1 do
+ local collision_stay = self.collision_stay[other_collision_class_name][i]
+ if collision_stay.collider.id == e.collider_2.id then table.remove(self.collision_stay[other_collision_class_name], i) end
+ end
+ end
+ self.exit_collision_data[other_collision_class_name] = {collider = e.collider_2, contact = e.contact}
+ return true
+ end
+ end
+ end
+end
+
+function Collider:getExitCollisionData(other_collision_class_name)
+ return self.exit_collision_data[other_collision_class_name]
+end
+
+function Collider:stay(other_collision_class_name)
+ if self.collision_stay[other_collision_class_name] then
+ if #self.collision_stay[other_collision_class_name] >= 1 then
+ return true
+ end
+ end
+end
+
+function Collider:getStayCollisionData(other_collision_class_name)
+ return self.collision_stay[other_collision_class_name]
+end
+
+function Collider:setPreSolve(callback)
+ self.preSolve = callback
+end
+
+function Collider:setPostSolve(callback)
+ self.postSolve = callback
+end
+
+function Collider:setObject(object)
+ self.object = object
+end
+
+function Collider:getObject()
+ return self.object
+end
+
+function Collider:addShape(shape_name, shape_type, ...)
+ if self.shapes[shape_name] or self.fixtures[shape_name] then error("Shape/fixture " .. shape_name .. " already exists.") end
+ local args = {...}
+ local shape = love.physics['new' .. shape_type](unpack(args))
+ local fixture = love.physics.newFixture(self.body, shape)
+ if self.world.masks[self.collision_class] then
+ fixture:setCategory(unpack(self.world.masks[self.collision_class].categories))
+ fixture:setMask(unpack(self.world.masks[self.collision_class].masks))
+ end
+ fixture:setUserData(self)
+ local sensor = love.physics.newFixture(self.body, shape)
+ sensor:setSensor(true)
+ sensor:setUserData(self)
+
+ self.shapes[shape_name] = shape
+ self.fixtures[shape_name] = fixture
+ self.sensors[shape_name] = sensor
+end
+
+function Collider:removeShape(shape_name)
+ if not self.shapes[shape_name] then return end
+ self.shapes[shape_name] = nil
+ self.fixtures[shape_name]:setUserData(nil)
+ self.fixtures[shape_name]:destroy()
+ self.fixtures[shape_name] = nil
+ self.sensors[shape_name]:setUserData(nil)
+ self.sensors[shape_name]:destroy()
+ self.sensors[shape_name] = nil
+end
+
+function Collider:destroy()
+ self.collision_stay = nil
+ self.enter_collision_data = nil
+ self.exit_collision_data = nil
+ self:collisionEventsClear()
+
+ self:setObject(nil)
+ for name, _ in pairs(self.fixtures) do
+ self.shapes[name] = nil
+ self.fixtures[name]:setUserData(nil)
+ self.fixtures[name] = nil
+ self.sensors[name]:setUserData(nil)
+ self.sensors[name] = nil
+ end
+ self.body:destroy()
+ self.body = nil
+end
+
+wf.World = World
+wf.Collider = Collider
+
+return wf
+
diff --git a/libs/windfield/mlib/Changes.txt b/libs/windfield/mlib/Changes.txt
new file mode 100644
index 0000000..4e7fc52
--- /dev/null
+++ b/libs/windfield/mlib/Changes.txt
@@ -0,0 +1,568 @@
+0.11.0
+====
+Added:
+----
+- mlib.vec2 component
+
+To-Do:
+----
+- Update README.md
+- Update spec.lua
+- Fix tabbing
+
+0.10.1
+====
+Added:
+----
+- Point category
+ - point.rotate
+ - point.scale
+ - point.polarToCartesian
+ - point.cartesianToPolar
+
+Changed:
+----
+- math.getPercent now returns decimals (instead of percentages) since those are more common to use.
+
+To-Do:
+----
+- Determine if isCompletelyInsideFunctions should return true with tangents.
+- Check argument order for logicality and consistency.
+- Add error checking.
+- Make sure to see if any aliases were missed. (e.g. isSegmentInside)
+- Clean up and correct README (add "Home" link, etc.)
+
+0.10.0
+====
+Added:
+----
+
+Changed:
+----
+- mlib.line.segment is now mlib.segment.
+- mlib.line.getIntercept has been renamed to mlib.line.getYIntercept
+- mlib.line.getYIntercept now returns the x-coordinate for vertical lines instead of false.
+- mlib.line.getYIntercept now returns the value `isVertical` as the second return value.
+- mlib.line.getPerpendicularBisector is now mlib.segment.getPerpendicularBisector.
+
+Fixed:
+----
+- mlib.line.getIntersection now should handle vertical slopes better.
+- mlib.line.getClosestPoint now uses local function checkFuzzy for checking horizontal lines.
+- Fixed possible bug in mlib.line.getSegmentIntersection and vertical lines.
+- mlib.segment.getIntersection now uses fuzzy checking for parallel lines.
+- mlib.math.round is now much more efficient.
+- Removed some useless code from mlib.polygon.isSegmentInside.
+
+To-Do:
+----
+- Determine if isCompletelyInsideFunctions should return true with tangents.
+- Check argument order for logicality and consistency.
+- Improve speed.
+- Add error checking.
+- Make sure to see if any aliases were missed. (e.g. isSegmentInside)
+- Implement mlib.shapes again(?)
+- Clean up and correct README (add "Home" link, etc.)
+
+0.9.4
+====
+Added:
+----
+
+Changed:
+----
+- mlib.line.getDistance is now slightly faster.
+- Made code much easier to debug by using new utility `cycle`.
+- Added new utility.
+- Various other minor changes.
+
+Removed:
+----
+- Unused local utility function copy
+
+To-Do
+----
+- Determine if isCompletelyInsideFunctions should return true with tangents.
+- Make argument order more logical.
+- Improve speed and error checking.
+- Make sure to see if any aliases were missed. (e.g. isSegmentInside)
+- Implement mlib.shapes again(?)
+- Clean up README (add "Home" link, etc.)
+
+0.9.3
+====
+Added:
+----
+- milb.circle.isCircleCompletelyInside
+- mlib.circle.isPolygonCompletelyInside
+- milb.circle.isSegmentCompletelyInside
+- mlib.polygon.isCircleCompletelyInside
+- mlib.polygon.isPolygonCompletelyInside
+- mlib.polygon.isSegmentCompletelyInside
+
+ - ALIASES -
+- mlib.circle.getPolygonIntersection
+- mlib.circle.isCircleInsidePolygon
+- mlib.circle.isCircleCompletelyInsidePolygon
+- milb.line.getCircleIntersection
+- milb.line.getPolygonIntersection
+- milb.line.getLineIntersection
+- mlib.line.segment.getCircleIntersection
+- mlib.line.segment.getPolygonIntersection
+- mlib.line.segment.getLineIntersection
+- mlib.line.segment.getSegmentIntersection
+- mlib.line.segment.isSegmentCompletelyInsideCircle
+- mlib.line.segment.isSegmentCompletelyInsidePolygon
+- mlib.polygon.isCircleCompletelyOver
+
+Changed:
+----
+- mlib.circle.getCircleIntersection now returns 'inside' instead of 'intersection' if the point has not intersections but is within the circle.
+- Fixed problem involving mlib.circle.getSegmentIntersection
+
+- README.md now has more information on how to run specs and other minor improvements.
+- Fixed some commenting on explanation of derivation of mlib.line.getIntersection.
+- Updated the example to use the current version of mlib.
+- Made/Changed some comments in the example main.lua.
+
+Removed:
+----
+
+To-Do
+----
+- Make examples file on github (examples/shapes/main.lua, etc.) not just one line.
+- Determine if isCompletelyInsideFunctions should return true with tangents.
+- Make argument order more logical.
+- Make sure to see if any aliases were missed. (e.g. isSegmentInside)
+- Update spec links in README
+
+0.9.2
+====
+Added:
+----
+
+Changed:
+----
+- mlib.polygon.getPolygonIntersection now does not create duplicate local table.
+- mlib.line.getPerpendicularSlope now does not create a global variable.
+- mlib.math.getSummation now allows the error to go through instead of returning false if the stop value is not a number.
+
+- Changed any instance of the term "userdata" with "input"
+
+Removed:
+----
+
+0.9.1
+====
+Added:
+----
+- Added mlib.statistics.getCentralTendency
+- Added mlib.statistics.getDispersion
+- Added mlib.statistics.getStandardDeviation
+- Added mlib.statistics.getVariation
+- Added mlib.statistics.getVariationRatio
+
+Removed:
+----
+
+Changed:
+----
+- FIX: mlib.polygon.checkPoint now handles vertices better.
+
+
+To-Do
+----
+- Add more functions.
+
+0.9.0
+====
+Added:
+----
+- mlib.line.getDistance as an alias for mlib.line.getLength.
+- mlib.line.checkPoint
+- Internal documentation.
+
+Removed:
+----
+- mlib.circle.isPointInCircle is replaced with mlib.circle.checkPoint
+- mlib.circle.checkPoint is replaced with mlib.circle.isPointOnCircle
+- Variation of mlib.circle.getLineIntersection( cx, cy, radius, slope, intercept ) is no longer supported, as it can cause errors with vertical lines.
+
+Changed:
+----
+- CHANGE: mlib.line.getIntersection now returns true for colinear lines.
+- CHANGE: mlib.line.getIntersection now returns true if the line are collinear.
+- CHANGE: mlib.line.getIntersection now returns true if vertical lines are collinear.
+- CHANGE: mlib.line.getSegmentIntersection now returns true if the line and segment are collinear.
+- CHANGE: Changed the order of mlib.line.segment.checkPoint arguments.
+- NAME: mlib.polygon.lineIntersects is now mlib.polygon.getLineIntersection
+- NAME: mlib.polygon.lineSegmentIntersects is now mlib.polygon.getSegmentIntersection
+- NAME: mlib.polygon.isLineSegmentInside is now mlib.polygon.isSegmentInside
+- NAME: mlib.polygon.polygonIntersects is now mlib.polygon.getPolygonIntersection
+- CHANGED: mlib.circle.checkPoint now takes arguments ( px, py, cx, cy, radius ).
+- CHANGED: mlib.circle.isPointOnCircle now takes arguments ( px, py, cx, cy, radius ).
+- NAME: mlib.polygon.circleIntersects is now mlib.polygon.getCircleIntersection
+- NAME: mlib.circle.isLineSecant is now mlib.circle.getLineIntersection
+- NAME: mlib.circle.isSegmentSecant is now mlib.circle.getSegmentIntersection
+- NAME: mlib.circle.circlesIntersects is now mlib.circle.getCircleIntersection
+- CHANGE: Added types 'tangent' and 'intersection' to mlib.circle.getCircleIntersection.
+- NAME: mlib.math.getRootsOfQuadratic is now mlib.math.getQuadraticRoots
+- CHANGE: mlib.math.getRoot now only returns the positive, since it there is not always negatives.
+- NAME: mlib.math.getPercent is now mlib.math.getPercentage
+
+- Cleaned up code (added comments, spaced lines, etc.)
+- Made syntax that uses camelCase instead of CamelCase.
+ - Match style of more programmers.
+ - Easier to type.
+- Moved to semantic numbering.
+- Made any returns strings lower-case.
+- Updated specs for missing functions.
+
+To-Do
+----
+- Update readme.
+- Add mlib.statistics.getStandardDeviation
+- Add mlib.statistics.getMeasuresOfCentralTendency
+- Add mlib.statistics.getMeasuresOfDispersion
+
+1.1.0.2
+====
+Added:
+----
+- MLib.Polygon.IsPolygonInside
+
+Removed:
+----
+- Removed all MLib.Shape:
+ - Was very slow.
+ - Could not define custom callbacks.
+ - Allow for flexibility.
+
+Changed:
+----
+- Switched MLib.Line.GetIntersection back to the old way
+- MLib.Line.GetSegmentIntersection now returns 4 values if the lines are parallel.
+
+TODO:
+- Make it so that MLib.Shape objects can use ':' syntax for other functions (i.e. MLib.Line.GetLength for Line objects, etc.)
+- Intuitive error messages.
+
+
+1.1.0.1
+====
+Added:
+----
+
+Removed:
+----
+
+Changed:
+- MLib.Line.GetIntersection now returns true, instead of two points.
+
+----
+
+Fixed:
+----
+- MLib.Line.GetIntersection now handles vertical lines: returns true if they collide, false otherwise.
+- MLib.Polygon.LineIntersects now also handles verticals.
+
+TODO:
+- Fix
+ - MLib.Shape Table can't have metatables.
+
+1.1.0.0
+====
+Added:
+----
+- MLib.Polygon.IsCircleInside
+- MLib.Polygon.LineSegmentIntersects
+- MLib.Polygon.IsLineSegmentInside
+- MLib.Statistics.GetFrequency
+- MLib.Math.Factorial
+- MLib.Math.SystemOfEquations
+
+Removed:
+----
+
+Changed:
+----
+- MLib.Polygon.LineIntersects is now MLib.Polygon.LineSegmentIntersects.
+- Put Word-wrap on Changes.txt
+
+Fixed:
+----
+- Problems with numberous MLib.Polygon and MLib.Circle problems.
+
+TODO:
+- Fix
+ - MLib.Shape Table can't have metatables.
+
+1.0.0.3
+====
+Added:
+----
+
+Removed:
+----
+
+Changed:
+----
+
+Fixed:
+----
+- README.md
+
+TODO:
+- Add:
+ - Frequency
+ - Binomial Probability
+ - Standard Deviation
+ - Conditional Probability
+
+1.0.0.2
+====
+Added:
+----
+
+Removed:
+----
+- Ability to use a direction for Math.GetAngle's 5th argument instead of having a third point. See Fixed for more.
+
+Changed:
+----
+- Changed README.md for clarity and consistency.
+- Updated spec.lua
+- See Fixed for more.
+
+Fixed:
+----
+- Circle.IsSegmentSecant now properly accounts for chords actually being chords, and not secants.
+- 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).
+- Statistics.GetMode now returns a table with the modes, and the second argument as the number of times they appear.
+- Math.GetRoot now returns the negative number as a second argument.
+- Math.GetPercentOfChange now works for 0 to 0 (previously false).
+- Math.GetAngle now takes only three points and no direction option.
+- Typos in Shape.CheckCollisions and Shape.Remove.
+- Fixed nil problems in Shape.CheckCollisions.
+- Improved readablility and DRYness of Shape.CheckCollisions.
+- Bugs in Shape.Remove and Shape.CheckCollisions regarding passing tables as arguments.
+
+TODO:
+- Add:
+ - Frequency
+ - Binomial Probability
+ - Standard Deviation
+ - Conditional Probability
+
+1.0.0.1
+====
+Added:
+----
+
+Removed:
+----
+
+Changed:
+----
+- Changes.txt now expanded to include short excertps from all previous commits.
+- Changed release number from 3.0.0 to 1.0.0.1
+- Math.Round now can round to decimal places as the second argument.
+- Commented unnecessary call of Segment.CheckPoint in Polygon.LineIntersects.
+- Polygon.LineIntersects now returns where the lines intersect.
+ - false if not intersection.
+ - A table with all of the intersections { { px, py } }
+- Same with Polygon.PolygonIntersects, Polygon.CircleIntersects,
+
+Fixed:
+----
+- Error with GetSlope being called incorrectly.
+- README.md Line.GetPerpendicularSlope misdirection.
+- 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.
+- Clarified README.md
+- Made util SortWithReferences local.
+- Errors caused by local functions.
+
+TODO:
+- Add:
+ - Frequency
+ - Binomial Probability
+ - Standard Deviation
+ - Conditional Probability
+
+3.0.0
+-----
+ADDED:
+- Added function GetSignedArea.
+REMOVED:
+- Removed drawing functions.
+- Removed MLib.Line.Functions entirely.
+CHANGED:
+- Changed all the names to CamelCase.
+- Changed module name to MLib.
+- Changed return order of GetPerpendicualrBisector from Slope, Midpoint to Midpoint, Slope.
+- Changed returned string of MLib.circle.isLineSecant to be upper-case.
+- Changed IsPrime to accept only one number at a time.
+- Changed NewShape's type to Capitals.
+
+Related to code:
+- Added more accuarate comments.
+- Made code more DRY.
+- Made code monkey-patchable and saved space (by declaring all functions as local values then inserted them into a large table.
+
+TODO:
+- Make LineIntersectsPolygon return where intersection occurs.
+- Ditto with PolygonIntersectsPolygon.
+- Add:
+ - Frequency
+ - Binomial Probability
+ - Standard Deviation
+ - Conditional Probability
+
+
+Not as accurately maintained before 2.0.2
+-----------------------------------------
+
+2.0.2
+-----
+- Cleaned up code, mostly.
+
+2.0.1
+-----
+- Bug fixes, mlib.shape:remove & demos added.
+
+2.0.0
+-----
+- Added mlib.shape and various bug fixes.
+
+2.0.0
+-----
+- Made mlib.shape and made numberous bug fixes.
+
+1.9.4
+-----
+- Made mlib.math.prime faster and removed ability to test multiple numbers at once. Thanks Robin!
+
+1.9.3
+-----
+- Fixed polygon.area and polygon.centroid
+
+1.9.2
+-----
+- Updated to LOVE 0.9.0.
+
+1.9.1
+-----
+- Made mlib.line.closestPoint able to take either two points on the slope or the slope and intercept.
+
+1.9.0
+-----
+- Added mlib.lineSegmentIntersects (no affiliation with previous one (changed to mlib.line.segment.intersect)) and mlib.line.closestPoint
+
+1.8.3
+-----
+- Changed naming mechanism to be more organized.
+
+1.8.2
+-----
+- "Fixed" mlib.lineSegmentsIntersect AGAIN!!!! :x
+
+1.8.1
+-----
+- Removed a print statement.
+
+1.8.0
+-----
+- mlib.pointInPolygon added
+
+1.7.5
+-----
+- mlib.lineSegmentsIntersect vertical lines fixed again. This time for real. I promise... or hope, at least... :P
+
+1.7.4
+-----
+- mlib.lineSegmentsIntersect vertical parallels fixed
+
+1.7.3
+-----
+- mlib.lineSegmentsIntersect parallels fixed
+
+1.7.2
+-----
+- mlib.lineSegmentsIntersect now handles vertical lines
+
+1.7.1
+-----
+- mlib.lineSegmentsIntersect now returns the two places in between where the line segments begin to intersect.
+
+1.7.0
+-----
+- Added mlib.circlesIntersect, mlib.pointOnLineSegment, mlib.linesIntersect, and mlib.lineSegmentsIntersect
+
+1.6.1
+-----
+- Employed usage of summations for mlib.getPolygonArea and mlib.getPolygonCentroid and removed area as an argument for mlib.getPolygonCentroid.
+
+1.6.0
+-----
+- Added several functions.
+
+1.5.0
+-----
+- Made lots of changes to syntax to make it easier to use (hopefully). I also put out specs.
+
+1.4.1
+-----
+- Localized mlib. Thanks, Yonaba!
+
+1.4.0
+-----
+- Added mlib.getPolygonCentroid (gets the midpoint of a non-self-intersecting polygons)
+
+1.3.2
+-----
+- Made mlib.getPrime take tables as arguments, so you can check all the values of a table.
+
+1.3.1
+-----
+- Changed name method to mlib.getPolygonArea
+
+1.3.0
+-----
+- Added mlib.get_polygon_area and removed mlib.get_convex_area and mlib.get_triangle_area since they are repetitive.
+
+1.2.2
+-----
+- Made functions return faster, functions that previously returned tables now return multiple arguments.
+
+1.2.1
+-----
+- Localized functions, made tables acceptable as arguments, refined function speed, mlib.get_mode now returns number most repeated as well as how many times.
+
+1.2.0
+-----
+- Added mlib.get_angle
+
+1.1.0
+-----
+- Added mlib.get_convex_area
+
+1.0.4
+-----
+- Fixed get_mode to handle bimodials.
+
+1.0.3
+-----
+- Prime Checker optimized (hopefully final update on this.)
+
+1.0.2
+-----
+- Prime checker now works! (At least to 1000. I haven't tested any
+further)
+
+1.0.1
+-----
+- 'Fixed' the prime checker
+
+1.0.0
+-----
+- Initial release
diff --git a/libs/windfield/mlib/LICENSE.md b/libs/windfield/mlib/LICENSE.md
new file mode 100644
index 0000000..38331e3
--- /dev/null
+++ b/libs/windfield/mlib/LICENSE.md
@@ -0,0 +1,17 @@
+Copyright (c) 2015 Davis Claiborne
+
+This software is provided 'as-is', without any express or implied
+warranty. In no event will the authors be held liable for any damages
+arising from the use of this software.
+
+Permission is granted to anyone to use this software for any purpose,
+including commercial applications, and to alter it and redistribute it
+freely, subject to the following restrictions:
+
+1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgement in the product documentation would be
+ appreciated but is not required.
+2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+3. This notice may not be removed or altered from any source distribution.
diff --git a/libs/windfield/mlib/README.md b/libs/windfield/mlib/README.md
new file mode 100644
index 0000000..6bbfdb2
--- /dev/null
+++ b/libs/windfield/mlib/README.md
@@ -0,0 +1,890 @@
+MLib
+====
+
+__MLib__ is a math and shape-intersection detection library written in Lua. It's aim is to be __robust__ and __easy to use__.
+
+__NOTE:__
+- 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).
+- 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).
+
+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.
+
+## Downloading
+You can download the latest __stable__ version of MLib by downloading the latest [release](../../releases/).
+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.
+
+## Implementing
+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.
+
+## Examples
+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.
+You can see some examples of the code in action [here](Examples).
+All examples are done using the *awesome* engine of [LÖVE](https://love2d.org/).
+To run them properly, download the [.love file](Examples/LOVE) and install LÖVE to your computer.
+After that, make sure you set .love files to open with "love.exe".
+For more, see [here](https://love2d.org/).
+
+## When should I use MLib?
+- If you need to know exactly where two objects intersect.
+- If you need general mathematical equations to be done.
+- If you need very precise details about point intersections.
+
+## When should I __not__ use MLib?
+- 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.
+- When you don't need precise information/odd shapes.
+
+## Specs
+#### For Windows
+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.
+
+#### Default
+Alternatively, you can find the tests [here](spec.lua). Keep in mind that you may need to change certain semantics to suit your OS.
+You can run them via [Telescope](https://github.com/norman/telescope/) and type the following command in the command-line of the root folder:
+```
+tsc -f specs.lua
+```
+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:
+```
+lua tsc -f specs.lua
+```
+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
+```
+cd C:\Path\to\telescope\
+```
+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.
+![Success](Reference Pictures/Success.png)
+
+## Functions
+- [mlib.line](#mlibline)
+ - [mlib.line.checkPoint](#mliblinecheckpoint)
+ - [mlib.line.getClosestPoint](#mliblinegetclosestpoint)
+ - [mlib.line.getYIntercept](#mliblinegetintercept)
+ - [mlib.line.getIntersection](#mliblinegetintersection)
+ - [mlib.line.getLength](#mliblinegetlength)
+ - [mlib.line.getMidpoint](#mliblinegetmidpoint)
+ - [mlib.line.getPerpendicularSlope](#mliblinegetperpendicularslope)
+ - [mlib.line.getSegmentIntersection](#mliblinegetsegmentintersection)
+ - [mlib.line.getSlope](#mliblinegetslope)
+- [mlib.segment](#mlibsegment)
+ - [mlib.segment.checkPoint](#mlibsegmentcheckpoint)
+ - [mlib.segment.getPerpendicularBisector](#mlibsegmentgetperpendicularbisector)
+ - [mlib.segment.getIntersection](#mlibsegmentgetintersection)
+- [mlib.polygon](#mlibpolygon)
+ - [mlib.polygon.checkPoint](#mlibpolygoncheckpoint)
+ - [mlib.polygon.getCentroid](#mlibpolygongetcentroid)
+ - [mlib.polygon.getCircleIntersection](#mlibpolygongetcircleintersection)
+ - [mlib.polygon.getLineIntersection](#mlibpolygongetlineintersection)
+ - [mlib.polygon.getPolygonArea](#mlibpolygongetpolygonarea)
+ - [mlib.polygon.getPolygonIntersection](#mlibpolygongetpolygonintersection)
+ - [mlib.polygon.getSegmentIntersection](#mlibpolygongetsegmentintersection)
+ - [mlib.polygon.getSignedPolygonArea](#mlibpolygongetsignedpolygonarea)
+ - [mlib.polygon.getTriangleHeight](#mlibpolygongettriangleheight)
+ - [mlib.polygon.isCircleInside](#mlibpolygoniscircleinside)
+ - [mlib.polygon.isCircleCompletelyInside](#mlibpolygoniscirclecompletelyinside)
+ - [mlib.polygon.isPolygonInside](#mlibpolygonispolygoninside)
+ - [mlib.polygon.isPolygonCompletelyInside](#mlibpolygonispolygoncompletelyinside)
+ - [mlib.polygon.isSegmentInside](#mlibpolygonissegmentinside)
+ - [mlib.polygon.isSegmentCompletelyInside](#mlibpolygonissegmentcompletelyinside)
+- [mlib.circle](#mlibcircle)
+ - [mlib.circle.checkPoint](#mlibcirclecheckpoint)
+ - [mlib.circle.getArea](#mlibcirclegetarea)
+ - [mlib.circle.getCircleIntersection](#mlibcirclegetcircleintersection)
+ - [mlib.circle.getCircumference](#mlibcirclegetcircumference)
+ - [mlib.circle.getLineIntersection](#mlibcirclegetlineintersection)
+ - [mlib.circle.getSegmentIntersection](#mlibcirclegetsegmentintersection)
+ - [mlib.circle.isCircleCompletelyInside](#mlibcircleiscirclecompletelyinside)
+ - [mlib.circle.isCircleCompletelyInsidePolygon](#mlibcircleiscirclecompletelyinsidepolygon)
+ - [mlib.circle.isPointOnCircle](#mlibcircleispointoncircle)
+ - [mlib.circle.isPolygonCompletelyInside](#mlibcircleispolygoncompletelyinside)
+- [mlib.statistics](#mlibstatistics)
+ - [mlib.statistics.getCentralTendency](#mlibstatisticsgetcentraltendency)
+ - [mlib.statistics.getDispersion](#mlibstatisticsgetdispersion)
+ - [mlib.statistics.getMean](#mlibstatisticsgetmean)
+ - [mlib.statistics.getMedian](#mlibstatisticsgetmedian)
+ - [mlib.statistics.getMode](#mlibstatisticsgetmode)
+ - [mlib.statistics.getRange](#mlibstatisticsgetrange)
+ - [mlib.statistics.getStandardDeviation](#mlibstatisticsgetstandarddeviation)
+ - [mlib.statistics.getVariance](#mlibstatisticsgetvariance)
+ - [mlib.statistics.getVariationRatio](#mlibstatisticsgetvariationratio)
+- [mlib.math](#mlibmath)
+ - [mlib.math.getAngle](#mlibmathgetangle)
+ - [mlib.math.getPercentage](#mlibmathgetpercentage)
+ - [mlib.math.getPercentOfChange](#mlibmathgetpercentofchange)
+ - [mlib.math.getQuadraticRoots](#mlibmathgetquadraticroots)
+ - [mlib.math.getRoot](#mlibmathgetroot)
+ - [mlib.math.getSummation](#mlibmathgetsummation)
+ - [mlib.math.isPrime](#mlibmathisprime)
+ - [mlib.math.round](#mlibmathround)
+- [Aliases](#aliases)
+
+#### mlib.line
+- Deals with linear aspects, such as slope and length.
+
+##### mlib.line.checkPoint
+- Checks if a point lies on a line.
+- Synopsis:
+ - `onPoint = mlib.line.checkPoint( px, px, x1, y1, x2, y2 )`
+- Arguments:
+ - `px`, `py`: Numbers. The x and y coordinates of the point being tested.
+ - `x1`, `y1`, `x2`, `y2`: Numbers. Two x and y coordinates of the line being tested.
+- Returns:
+ - `onPoint`: Boolean.
+ - `true` if the point is on the line.
+ - `false` if it does not.
+- Notes:
+ - You cannot use the format `mlib.line.checkPoint( px, px, slope, intercept )` because this would lead to errors on vertical lines.
+
+##### mlib.line.getClosestPoint
+- Gives the closest point to a line.
+- Synopses:
+ - `cx, cy = mlib.line.getClosestPoint( px, py, x1, y1, x2, y2 )`
+ - `cx, cy = mlib.line.getClosestPoint( px, py, slope, intercept )`
+- Arguments:
+ - `x`, `y`: Numbers. The x and y coordinates of the point.
+ - `x1`, `y1`, `x2`, `y2`: Numbers. Two x and y coordinates on the line.
+ - `slope`, `intercept`:
+ - Numbers. The slope and y-intercept of the line.
+ - Booleans (`false`). The slope and y-intercept of a vertical line.
+- Returns:
+ - `cx`, `cy`: Numbers. The closest points that lie on the line to the point.
+
+##### mlib.line.getYIntercept
+- Gives y-intercept of the line.
+- Synopses:
+ - `intercept, isVertical = mlib.line.getYIntercept( x1, y1, x2, y2 )`
+ - `intercept, isVertical = mlib.line.getYIntercept( x1, y1, slope )`
+- Arguments:
+ - `x1`, `y1`, `x2`, `y2`: Numbers. Two x and y coordinates that lie on the line.
+ - `slope`:
+ - Number. The slope of the line.
+- Returns:
+ - `intercept`:
+ - Number. The y-intercept of the line.
+ - Number. The `x1` coordinate of the line if the line is vertical.
+ - `isVertical`:
+ - Boolean. `true` if the line is vertical, `false` if the line is not vertical.
+
+##### mlib.line.getIntersection
+- Gives the intersection of two lines.
+- Synopses:
+ - `x, y = mlib.line.getIntersection( x1, y1, x2, y2, x3, y3, x4, y4 )`
+ - `x, y = mlib.line.getIntersection( slope1, intercept1, x3, y3, x4, y4 )`
+ - `x, y = mlib.line.getIntersection( slope1, intercept1, slope2, intercept2 )`
+- Arguments:
+ - `x1`, `y1`, `x2`, `y2`: Numbers. Two x and y coordinates that lie on the first line.
+ - `x3`, `y3`, `x4`, `y4`: Numbers. Two x and y coordinates that lie on the second line.
+ - `slope1`, `intercept1`:
+ - Numbers. The slope and y-intercept of the first line.
+ - Booleans (`false`). The slope and y-intercept of the first line (if the first line is vertical).
+ - `slope2`, `intercept2`:
+ - Numbers. The slope and y-intercept of the second line.
+ - Booleans (`false`). The slope and y-intercept of the second line (if the second line is vertical).
+- Returns:
+ - `x`, `y`:
+ - Numbers. The x and y coordinate where the lines intersect.
+ - Boolean:
+ - `true`, `nil`: The lines are collinear.
+ - `false`, `nil`: The lines are parallel and __not__ collinear.
+
+##### mlib.line.getLength
+- Gives the distance between two points.
+- Synopsis:
+ - `length = mlib.line.getLength( x1, y1, x2, y2 )
+- Arguments:
+ - `x1`, `y1`, `x2`, `y2`: Numbers. Two x and y coordinates.
+- Returns:
+ - `length`: Number. The distance between the two points.
+
+##### mlib.line.getMidpoint
+- Gives the midpoint of two points.
+- Synopsis:
+ - `x, y = mlib.line.getMidpoint( x1, y1, x2, y2 )`
+- Arguments:
+ - `x1`, `y1`, `x2`, `y2`: Numbers. Two x and y coordinates.
+- Returns:
+ - `x`, `y`: Numbers. The midpoint x and y coordinates.
+
+##### mlib.line.getPerpendicularSlope
+- Gives the perpendicular slope of a line.
+- Synopses:
+ - `perpSlope = mlib.line.getPerpendicularSlope( x1, y1, x2, y2 )`
+ - `perpSlope = mlib.line.getPerpendicularSlope( slope )`
+- Arguments:
+ - `x1`, `y1`, `x2`, `y2`: Numbers. Two x and y coordinates.
+ - `slope`: Number. The slope of the line.
+- Returns:
+ - `perpSlope`:
+ - Number. The perpendicular slope of the line.
+ - Boolean (`false`). The perpendicular slope of the line (if the original line was horizontal).
+
+##### mlib.line.getSegmentIntersection
+- Gives the intersection of a line segment and a line.
+- Synopses:
+ - `x1, y1, x2, y2 = mlib.line.getSegmentIntersection( x1, y1, x2, y2, x3, y3, x4, y4 )`
+ - `x1, y1, x2, y2 = mlib.line.getSegmentIntersection( x1, y1, x2, y2, slope, intercept )`
+- Arguments:
+ - `x1`, `y1`, `x2`, `y2`: Numbers. Two x and y coordinates that lie on the line segment.
+ - `x3`, `y3`, `x4`, `y4`: Numbers. Two x and y coordinates that lie on the line.
+ - `slope`, `intercept`:
+ - Numbers. The slope and y-intercept of the the line.
+ - Booleans (`false`). The slope and y-intercept of the line (if the line is vertical).
+- Returns:
+ - `x1`, `y1`, `x2`, `y2`:
+ - Number, Number, Number, Number.
+ - The points of the line segment if the line and segment are collinear.
+ - Number, Number, Boolean (`nil`), Boolean (`nil`).
+ - The coordinate of intersection if the line and segment intersect and are not collinear.
+ - Boolean (`false`), Boolean (`nil`), Boolean (`nil`),
+ - Boolean (`nil`). If the line and segment don't intersect.
+
+##### mlib.line.getSlope
+- Gives the slope of a line.
+- Synopsis:
+ - `slope = mlib.line.getSlope( x1, y1, x2, y2 )
+- Arguments:
+ - `x1`, `y1`, `x2`, `y2`: Numbers. Two x and y coordinates.
+- Returns:
+ - `slope`:
+ - Number. The slope of the line.
+ - Boolean (`false`). The slope of the line (if the line is vertical).
+
+#### mlib.segment
+- Deals with line segments.
+
+##### mlib.segment.checkPoint
+- Checks if a point lies on a line segment.
+- Synopsis:
+ - `onSegment = mlib.segment.checkPoint( px, py, x1 y1, x2, y2 )`
+- Arguments:
+ - `px`, `py`: Numbers. The x and y coordinates of the point being checked.
+ - `x1`, `y1`, `x2`, `y2`: Numbers. Two x and y coordinates.
+- Returns:
+ - `onSegment`: Boolean.
+ - `true` if the point lies on the line segment.
+ - `false` if the point does not lie on the line segment.
+
+##### mlib.segment.getPerpendicularBisector
+- Gives the perpendicular bisector of a line.
+- Synopsis:
+ - `x, y, slope = mlib.segment.getPerpendicularBisector( x1, y1, x2, y2 )`
+- Arguments:
+ - `x1`, `y1`, `x2`, `y2`: Numbers. Two x and y coordinates.
+- Returns:
+ - `x`, `y`: Numbers. The midpoint of the line.
+ - `slope`:
+ - Number. The perpendicular slope of the line.
+ - Boolean (`false`). The perpendicular slope of the line (if the original line was horizontal).
+
+##### mlib.segment.getIntersection
+- Checks if two line segments intersect.
+- Synopsis:
+ - `cx1, cy1, cx2, cy2 = mlib.segment.getIntersection( x1, y1, x2, y2, x3, y3 x4, y4 )`
+- Arguments:
+ - `x1`, `y1`, `x2`, `y2`: Numbers. Two x and y coordinates of the first line segment.
+ - `x3`, `y3`, `x4`, `y4`: Numbers. Two x and y coordinates of the second line segment.
+- Returns:
+ - `cx1`, `cy1`, `cx2`, `cy2`:
+ - Number, Number, Number, Number.
+ - The points of the resulting intersection if the line segments are collinear.
+ - Number, Number, Boolean (`nil`), Boolean (`nil`).
+ - The point of the resulting intersection if the line segments are not collinear.
+ - Boolean (`false`), Boolean (`nil`), Boolean (`nil`) , Boolean (`nil`).
+ - If the line segments don't intersect.
+
+#### mlib.polygon
+- Handles aspects involving polygons.
+
+##### mlib.polygon.checkPoint
+- Checks if a point is inside of a polygon.
+- Synopses:
+ - `inPolygon = mlib.polygon.checkPoint( px, py, vertices )`
+ - `inPolygon = mlib.polygon.checkPoint( px, py, ... )`
+- Arguments:
+ - `px`, `py`: Numbers. The x and y coordinate of the point being checked.
+ - `vertices`: Table. The vertices of the polygon in the format `{ x1, y1, x2, y2, x3, y3, ... }`
+ - `...`: Numbers. The x and y coordinates of the polygon. (Same as using `unpack( vertices )`)
+- Returns:
+ - `inPolygon`: Boolean.
+ - `true` if the point is inside the polygon.
+ - `false` if the point is not inside the polygon.
+
+##### mlib.polygon.getCentroid
+- Returns the centroid of the polygon.
+- Synopses:
+ - `cx, cy = mlib.polygon.getCentroid( vertices )`
+ - `cx, cy = mlib.polygon.getCentroid( ... )`
+- Arguments:
+ - `vertices`: Table. The vertices of the polygon in the format `{ x1, y1, x2, y2, x3, y3, ... }`
+ - `...`: Numbers. The x and y coordinates of the polygon. (Same as using `unpack( vertices )`)
+- Returns:
+ - `cx`, `cy`: Numbers. The x and y coordinates of the centroid.
+
+##### mlib.polygon.getCircleIntersection
+- Returns the coordinates of where a circle intersects a polygon.
+- Synopses:
+ - `intersections = mlib.polygon.getCircleIntersection( cx, cy, radius, vertices )`
+ - `intersections = mlib.polygon.getCircleIntersection( cx, cy, radius, ... )
+- Arguments:
+ - `cx`, `cy`: Number. The coordinates of the center of the circle.
+ - `radius`: Number. The radius of the circle.
+ - `vertices`: Table. The vertices of the polygon in the format `{ x1, y1, x2, y2, x3, y3, ... }`
+ - `...`: Numbers. The x and y coordinates of the polygon. (Same as using `unpack( vertices )`)
+- Returns:
+ - `intersections`: Table. Contains the intersections and type.
+- Example:
+```lua
+local tab = _.polygon.getCircleIntersection( 5, 5, 1, 4, 4, 6, 4, 6, 6, 4, 6 )
+for i = 1, # tab do
+ print( i .. ':', unpack( tab[i] ) )
+end
+-- 1: tangent 5 4
+-- 2: tangent 6 5
+-- 3: tangent 5 6
+-- 4: tagnent 4 5
+```
+- For more see [mlib.circle.getSegmentIntersection](#mlibcirclegetsegmentintersection) or the [specs](spec.lua# L676)
+
+##### mlib.polygon.getLineIntersection
+- Returns the coordinates of where a line intersects a polygon.
+- Synopses:
+ - `intersections = mlib.polygon.getLineIntersection( x1, y1, x2, y2, vertices )`
+ - `intersections = mlib.polygon.getLineIntersection( x1, y1, x2, y2, ... )
+- Arguments:
+ - `x1`, `y1`, `x2`, `y2`: Numbers. Two x and y coordinates.
+ - `vertices`: Table. The vertices of the polygon in the format `{ x1, y1, x2, y2, x3, y3, ... }`
+ - `...`: Numbers. The x and y coordinates of the polygon. (Same as using `unpack( vertices )`)
+- Returns:
+ - `intersections`: Table. Contains the intersections.
+- Notes:
+ - With collinear lines, they are actually broken up. i.e. `{ 0, 4, 0, 0 }` would become `{ 0, 4 }, { 0, 0 }`.
+
+##### mlib.polygon.getPolygonArea
+- Gives the area of a polygon.
+- Synopses:
+ - `area = mlib.polygon.getArea( vertices )`
+ - `area = mlib.polygon.getArea( ... )
+- Arguments:
+ - `vertices`: Table. The vertices of the polygon in the format `{ x1, y1, x2, y2, x3, y3, ... }`
+ - `...`: Numbers. The x and y coordinates of the polygon. (Same as using `unpack( vertices )`)
+- Returns:
+ - `area`: Number. The area of the polygon.
+
+##### mlib.polygon.getPolygonIntersection
+- Gives the intersection of two polygons.
+- Synopsis:
+ - `intersections = mlib.polygon.getPolygonIntersections( polygon1, polygon2 )`
+- Arguments:
+ - `polygon1`: Table. The vertices of the first polygon in the format `{ x1, y1, x2, y2, x3, y3, ... }`
+ - `polygon2`: Table. The vertices of the second polygon in the format `{ x1, y1, x2, y2, x3, y3, ... }`
+- Returns:
+ - `intersections`: Table. A table of the points of intersection.
+
+##### mlib.polygon.getSegmentIntersection
+- Returns the coordinates of where a line segmeing intersects a polygon.
+- Synopses:
+ - `intersections = mlib.polygon.getSegmentIntersection( x1, y1, x2, y2, vertices )`
+ - `intersections = mlib.polygon.getSegmentIntersection( x1, y1, x2, y2, ... )
+- Arguments:
+ - `x1`, `y1`, `x2`, `y2`: Numbers. Two x and y coordinates.
+ - `vertices`: Table. The vertices of the polygon in the format `{ x1, y1, x2, y2, x3, y3, ... }`
+ - `...`: Numbers. The x and y coordinates of the polygon. (Same as using `unpack( vertices )`)
+- Returns:
+ - `intersections`: Table. Contains the intersections.
+- Notes:
+ - With collinear line segments, they are __not__ broken up. See the [specs](spec.lua# L508) for more.
+
+##### mlib.polygon.getSignedPolygonArea
+- 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.
+- Synopses:
+ - `area = mlib.polygon.getLineIntersection( vertices )`
+ - `area = mlib.polygon.getLineIntersection( ... )
+- Arguments:
+ - `vertices`: Table. The vertices of the polygon in the format `{ x1, y1, x2, y2, x3, y3, ... }`
+ - `...`: Numbers. The x and y coordinates of the polygon. (Same as using `unpack( vertices )`)
+- Returns:
+ - `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.
+
+##### mlib.polygon.getTriangleHeight
+- Gives the height of a triangle.
+- Synopses:
+ - `height = mlib.polygon.getTriangleHeigh( base, x1, y1, x2, y2, x3, y3 )`
+ - `height = mlib.polygon.getTriangleHeight( base, area )`
+- Arguments:
+ - `base`: Number. The length of the base of the triangle.
+ - `x1`, `y1`, `x2`, `y2`, `x3`, `y3`: Numbers. The x and y coordinates of the triangle.
+ - `area`: Number. The regular area of the triangle. __Not__ the signed area.
+- Returns:
+ - `height`: Number. The height of the triangle.
+
+##### mlib.polygon.isCircleInside
+- Checks if a circle is inside the polygon.
+- Synopses:
+ - `inPolygon = mlib.polygon.isCircleInside( cx, cy, radius, vertices )`
+ - `inPolygon = mlib.polygon.isCircleInside( cx, cy, radius, ... )`
+- Arguments:
+ - `cx`, `cy`: Numbers. The x and y coordinates for the center of the circle.
+ - `radius`: Number. The radius of the circle.
+ - `vertices`: Table. The vertices of the polygon in the format `{ x1, y1, x2, y2, x3, y3, ... }`
+ - `...`: Numbers. The x and y coordinates of the polygon. (Same as using `unpack( vertices )`)
+- Returns:
+ - `inPolygon`: Boolean.
+ - `true` if the circle is inside the polygon.
+ - `false` if the circle is not inside the polygon.
+- Notes:
+ - Only returns true if the center of the circle is inside the circle.
+
+##### mlib.polygon.isCircleCompletelyInside
+- Checks if a circle is completely inside the polygon.
+- Synopses:
+ - `inPolygon = mlib.polygon.isCircleCompletelyInside( cx, cy, radius, vertices )`
+ - `inPolygon = mlib.polygon.isCircleCompletelyInside( cx, cy, radius, ... )`
+- Arguments:
+ - `cx`, `cy`: Numbers. The x and y coordinates for the center of the circle.
+ - `radius`: Number. The radius of the circle.
+ - `vertices`: Table. The vertices of the polygon in the format `{ x1, y1, x2, y2, x3, y3, ... }`
+ - `...`: Numbers. The x and y coordinates of the polygon. (Same as using `unpack( vertices )`)
+- Returns:
+ - `inPolygon`: Boolean.
+ - `true` if the circle is __completely__ inside the polygon.
+ - `false` if the circle is not inside the polygon.
+
+##### mlib.polygon.isPolygonInside
+- Checks if a polygon is inside a polygon.
+- Synopsis:
+ - `inPolygon = mlib.polygon.isPolygonInside( polygon1, polygon2 )`
+- Arguments:
+ - `polygon1`: Table. The vertices of the first polygon in the format `{ x1, y1, x2, y2, x3, y3, ... }`
+ - `polygon2`: Table. The vertices of the second polygon in the format `{ x1, y1, x2, y2, x3, y3, ... }`
+- Returns:
+ - `inPolygon`: Boolean.
+ - `true` if the `polygon2` is inside of `polygon1`.
+ - `false` if `polygon2` is not inside of `polygon2`.
+- Notes:
+ - Returns true as long as any of the line segments of `polygon2` are inside of the `polygon1`.
+
+##### mlib.polygon.isPolygonCompletelyInside
+- Checks if a polygon is completely inside a polygon.
+- Synopsis:
+ - `inPolygon = mlib.polygon.isPolygonCompletelyInside( polygon1, polygon2 )`
+- Arguments:
+ - `polygon1`: Table. The vertices of the first polygon in the format `{ x1, y1, x2, y2, x3, y3, ... }`
+ - `polygon2`: Table. The vertices of the second polygon in the format `{ x1, y1, x2, y2, x3, y3, ... }`
+- Returns:
+ - `inPolygon`: Boolean.
+ - `true` if the `polygon2` is __completely__ inside of `polygon1`.
+ - `false` if `polygon2` is not inside of `polygon2`.
+
+##### mlib.polygon.isSegmentInside
+- Checks if a line segment is inside a polygon.
+- Synopses:
+ - `inPolygon = mlib.polygon.isSegmentInside( x1, y1, x2, y2, vertices )`
+ - `inPolygon = mlib.polygon.isSegmentInside( x1, y1, x2, y2, ... )`
+- Arguments:
+ - `x1`, `y1`, `x2`, `y2`: Numbers. The x and y coordinates of the line segment.
+ - `vertices`: Table. The vertices of the polygon in the format `{ x1, y1, x2, y2, x3, y3, ... }`
+ - `...`: Numbers. The x and y coordinates of the polygon. (Same as using `unpack( vertices )`)
+- Returns:
+ - `inPolygon`: Boolean.
+ - `true` if the line segment is inside the polygon.
+ - `false` if the line segment is not inside the polygon.
+- Note:
+ - Only one of the points has to be in the polygon to be considered 'inside' of the polygon.
+ - This is really just a faster version of [mlib.polygon.getPolygonIntersection](#mlibpolygongetpolygonintersection) that does not give the points of intersection.
+
+##### mlib.polygon.isSegmentCompletelyInside
+- Checks if a line segment is completely inside a polygon.
+- Synopses:
+ - `inPolygon = mlib.polygon.isSegmentCompletelyInside( x1, y1, x2, y2, vertices )`
+ - `inPolygon = mlib.polygon.isSegmentCompletelyInside( x1, y1, x2, y2, ... )`
+- Arguments:
+ - `x1`, `y1`, `x2`, `y2`: Numbers. The x and y coordinates of the line segment.
+ - `vertices`: Table. The vertices of the polygon in the format `{ x1, y1, x2, y2, x3, y3, ... }`
+ - `...`: Numbers. The x and y coordinates of the polygon. (Same as using `unpack( vertices )`)
+- Returns:
+ - `inPolygon`: Boolean.
+ - `true` if the line segment is __completely__ inside the polygon.
+ - `false` if the line segment is not inside the polygon.
+
+#### mlib.circle
+- Handles aspects involving circles.
+
+##### mlib.circle.checkPoint
+- Checks if a point is on the inside or on the edge the circle.
+- Synopsis:
+ - `inCircle = mlib.circle.checkPoint( px, px, cx, cy, radius )`
+- Arguments:
+ - `px`, `py`: Numbers. The x and y coordinates of the point being tested.
+ - `cx`, `cy`: Numbers. The x and y coordinates of the center of the circle.
+ - `radius`: Number. The radius of the circle.
+- Returns:
+ - `inCircle`: Boolean.
+ - `true` if the point is inside or on the circle.
+ - `false` if the point is outside of the circle.
+
+##### mlib.circle.getArea
+- Gives the area of a circle.
+- Synopsis:
+ - `area = mlib.circle.getArea( radius )`
+- Arguments:
+ - `radius`: Number. The radius of the circle.
+- Returns:
+ - `area`: Number. The area of the circle.
+
+##### mlib.circle.getCircleIntersection
+- Gives the intersections of two circles.
+- Synopsis:
+ - `intersections = mlib.circle.getCircleIntersection( c1x, c1y, radius1, c2x, c2y, radius2 )
+- Arguments:
+ - `c1x`, `c1y`: Numbers. The x and y coordinate of the first circle.
+ - `radius1`: Number. The radius of the first circle.
+ - `c2x`, `c2y`: Numbers. The x and y coordinate of the second circle.
+ - `radius2`: Number. The radius of the second circle.
+- Returns:
+ - `intersections`: Table. A table that contains the type and where the circle collides. See the [specs](spec.lua# L698) for more.
+
+##### mlib.circle.getCircumference
+- Returns the circumference of a circle.
+- Synopsis:
+ - `circumference = mlib.circle.getCircumference( radius )`
+- Arguments:
+ - `radius`: Number. The radius of the circle.
+- Returns:
+ - `circumference`: Number. The circumference of a circle.
+
+##### mlib.circle.getLineIntersection
+- Returns the intersections of a circle and a line.
+- Synopsis:
+ - `intersections = mlib.circle.getLineIntersections( cx, cy, radius, x1, y1, x2, y2 )`
+- Arguments:
+ - `cx`, `cy`: Numbers. The x and y coordinates for the center of the circle.
+ - `radius`: Number. The radius of the circle.
+ - `x1`, `y1`, `x2`, `y2`: Numbers. Two x and y coordinates the lie on the line.
+- Returns:
+ - `intersections`: Table. A table with the type and where the intersections happened. Table is formatted:
+ - `type`, `x1`, `y1`, `x2`, `y2`
+ - String (`'secant'`), Number, Number, Number, Number
+ - The numbers are the x and y coordinates where the line intersects the circle.
+ - String (`'tangent'`), Number, Number, Boolean (`nil`), Boolean (`nil`)
+ - `x1` and `x2` represent where the line intersects the circle.
+ - Boolean (`false`), Boolean (`nil`), Boolean (`nil`), Boolean (`nil`), Boolean (`nil`)
+ - No intersection.
+ - For more see the [specs](spec.lua# L660).
+
+##### mlib.circle.getSegmentIntersection
+- Returns the intersections of a circle and a line segment.
+- Synopsis:
+ - `intersections = mlib.circle.getSegmentIntersections( cx, cy, radius, x1, y1, x2, y2 )`
+- Arguments:
+ - `cx`, `cy`: Numbers. The x and y coordinates for the center of the circle.
+ - `radius`: Number. The radius of the circle.
+ - `x1`, `y1`, `x2`, `y2`: Numbers. The two x and y coordinates of the line segment.
+- Returns:
+ - `intersections`: Table. A table with the type and where the intersections happened. Table is formatted:
+ - `type`, `x1`, `y1`, `x2`, `y2`
+ - String (`'chord'`), Number, Number, Number, Number
+ - The numbers are the x and y coordinates where the line segment is on both edges of the circle.
+ - String (`'enclosed'`), Number, Number, Number, Number
+ - The numbers are the x and y coordinates of the line segment if it is fully inside of the circle.
+ - String (`'secant'`), Number, Number, Number, Number
+ - The numbers are the x and y coordinates where the line segment intersects the circle.
+ - String (`'tangent'`), Number, Number, Boolean (`nil`), Boolean (`nil`)
+ - `x1` and `x2` represent where the line segment intersects the circle.
+ - Boolean (`false`), Boolean (`nil`), Boolean (`nil`), Boolean (`nil`), Boolean (`nil`)
+ - No intersection.
+ - For more see the [specs](spec.lua# L676).
+
+##### mlib.circle.isCircleCompletelyInside
+- Checks if one circle is completely inside of another circle.
+- Synopsis:
+ - `completelyInside = mlib.circle.isCircleCompletelyInside( c1x, c1y, c1radius, c2x, c2y, c2radius )`
+- Arguments:
+ - `c1x`, `c1y`: Numbers. The x and y coordinates of the first circle.
+ - `c1radius`: Number. The radius of the first circle.
+ - `c2x`, `c2y`: Numbers. The x and y coordinates of the second circle.
+ - `c2radius`: Number. The radius of the second circle.
+- Returns:
+ - `completelyInside`: Boolean.
+ - `true` if circle1 is inside of circle2.
+ - `false` if circle1 is not __completely__ inside of circle2.
+
+##### mlib.circle.isCircleCompletelyInsidePolygon
+- Checks if a circle is completely inside the polygon.
+- Synopses:
+ - `inPolygon = mlib.polygon.isCircleCompletelyInside( cx, cy, radius, vertices )`
+ - `inPolygon = mlib.polygon.isCircleCompletelyInside( cx, cy, radius, ... )`
+- Arguments:
+ - `cx`, `cy`: Numbers. The x and y coordinates for the center of the circle.
+ - `radius`: Number. The radius of the circle.
+ - `vertices`: Table. The vertices of the polygon in the format `{ x1, y1, x2, y2, x3, y3, ... }`
+ - `...`: Numbers. The x and y coordinates of the polygon. (Same as using `unpack( vertices )`)
+- Returns:
+ - `inPolygon`: Boolean.
+ - `true` if the circle is __completely__ inside the polygon.
+ - `false` if the circle is not inside the polygon.
+
+##### mlib.circle.isPointOnCircle
+- Checks if a point is __exactly__ on the edge of the circle.
+- Synopsis:
+ - `onCircle = mlib.circle.checkPoint( px, px, cx, cy, radius )`
+- Arguments:
+ - `px`, `py`: Numbers. The x and y coordinates of the point being tested.
+ - `cx`, `cy`: Numbers. The x and y coordinates of the center of the circle.
+ - `radius`: Number. The radius of the circle.
+- Returns:
+ - `onCircle`: Boolean.
+ - `true` if the point is on the circle.
+ - `false` if the point is on the inside or outside of the circle.
+- Notes:
+ - Will return false if the point is inside __or__ outside of the circle.
+
+##### mlib.circle.isPolygonCompletelyInside
+- Checks if a polygon is completely inside of a circle.
+- Synopsis:
+ - `completelyInside = mlib.circle.isPolygonCompletelyInside( circleX, circleY, circleRadius, vertices )`
+ - `completelyInside = mlib.circle.isPolygonCompletelyInside( circleX, circleY, circleRadius, ... )`
+- Arguments:
+ - `circleX`, `circleY`: Numbers. The x and y coordinates of the circle.
+ - `circleRadius`: Number. The radius of the circle.
+ - `vertices`: Table. A table containing all of the vertices of the polygon.
+ - `...`: Numbers. All of the points of the polygon.
+- Returns:
+ - `completelyInside`: Boolean.
+ - `true` if the polygon is inside of the circle.
+ - `false` if the polygon is not __completely__ inside of the circle.
+
+#### mlib.statistics
+- Handles statistical aspects of math.
+
+##### mlib.statistics.getCentralTendency
+- Gets the central tendency of the data.
+- Synopses:
+ - `modes, occurrences, median, mean = mlib.statistics.getCentralTendency( data )`
+ - `modes, occurrences, median, mean = mlib.statistics.getCentralTendency( ... )`
+- Arguments:
+ - `data`: Table. A table containing the values of data.
+ - `...`: Numbers. All of the numbers in the data set.
+- Returns:
+ - `modes, occurrences`: Table, Number. The modes of the data and the number of times it occurs. See [mlib.statistics.getMode](#mlibstatisticsgetmode).
+ - `median`: Number. The median of the data set.
+ - `mean`: Number. The mean of the data set.
+
+##### mlib.statistics.getDispersion
+- Gets the dispersion of the data.
+- Synopses:
+ - `variationRatio, range, standardDeviation = mlib.statistics.getDispersion( data )`
+ - `variationRatio, range, standardDeviation = mlib.statistics.getDispersion( ... )`
+- Arguments:
+ - `data`: Table. A table containing the values of data.
+ - `...`: Numbers. All of the numbers in the data set.
+- Returns:
+ - `variationRatio`: Number. The variation ratio of the data set.
+ - `range`: Number. The range of the data set.
+ - `standardDeviation`: Number. The standard deviation of the data set.
+
+##### mlib.statistics.getMean
+- Gets the arithmetic mean of the data.
+- Synopses:
+ - `mean = mlib.statistics.getMean( data )`
+ - `mean = mlib.statistics.getMean( ... )`
+- Arguments:
+ - `data`: Table. A table containing the values of data.
+ - `...`: Numbers. All of the numbers in the data set.
+- Returns:
+ - `mean`: Number. The arithmetic mean of the data set.
+
+##### mlib.statistics.getMedian
+- Gets the median of the data set.
+- Synopses:
+ - `median = mlib.statistics.getMedian( data )`
+ - `median = mlib.statistics.getMedian( ... )`
+- Arguments:
+ - `data`: Table. A table containing the values of data.
+ - `...`: Numbers. All of the numbers in the data set.
+- Returns:
+ - `median`: Number. The median of the data.
+
+##### mlib.statistics.getMode
+- Gets the mode of the data set.
+- Synopses:
+ - `mode, occurrences = mlib.statistics.getMode( data )`
+ - `mode, occurrences = mlib.statistics.getMode( ... )`
+- Arguments:
+ - `data`: Table. A table containing the values of data.
+ - `...`: Numbers. All of the numbers in the data set.
+- Returns:
+ - `mode`: Table. The mode(s) of the data.
+ - `occurrences`: Number. The number of time the mode(s) occur.
+
+##### mlib.statistics.getRange
+- Gets the range of the data set.
+- Synopses:
+ - `range = mlib.statistics.getRange( data )`
+ - `range = mlib.statistics.getRange( ... )`
+- Arguments:
+ - `data`: Table. A table containing the values of data.
+ - `...`: Numbers. All of the numbers in the data set.
+- Returns:
+ - `range`: Number. The range of the data.
+
+##### mlib.statistics.getStandardDeviation
+- Gets the standard deviation of the data.
+- Synopses:
+ - `standardDeviation = mlib.statistics.getStandardDeviation( data )`
+ - `standardDeviation = mlib.statistics.getStandardDeviation( ... )`
+- Arguments:
+ - `data`: Table. A table containing the values of data.
+ - `...`: Numbers. All of the numbers in the data set.
+- Returns:
+ - `standardDeviation`: Number. The standard deviation of the data set.
+
+##### mlib.statistics.getVariance
+- Gets the variation of the data.
+- Synopses:
+ - `variance = mlib.statistics.getVariance( data )`
+ - `variance = mlib.statistics.getVariance( ... )`
+- Arguments:
+ - `data`: Table. A table containing the values of data.
+ - `...`: Numbers. All of the numbers in the data set.
+- Returns:
+ - `variance`: Number. The variation of the data set.
+
+##### mlib.statistics.getVariationRatio
+- Gets the variation ratio of the data.
+- Synopses:
+ - `variationRatio = mlib.statistics.getVariationRatio( data )`
+ - `variationRatio = mlib.statistics.getVariationRatio( ... )`
+- Arguments:
+ - `data`: Table. A table containing the values of data.
+ - `...`: Numbers. All of the numbers in the data set.
+- Returns:
+ - `variationRatio`: Number. The variation ratio of the data set.
+
+#### mlib.math
+- Miscellaneous functions that have no home.
+
+##### mlib.math.getAngle
+- Gets the angle between three points.
+- Synopsis:
+ - `angle = mlib.math.getAngle( x1, y1, x2, y2, x3, y3 )`
+- Arguments:
+ - `x1`, `y1`: Numbers. The x and y coordinates of the first point.
+ - `x2`, `y2`: Numbers. The x and y coordinates of the vertex of the two points.
+ - `x3`, `y3`: Numbers. The x and y coordinates of the second point.
+
+##### mlib.math.getPercentage
+- Gets the percentage of a number.
+- Synopsis:
+ - `percentage = mlib.math.getPercentage( percent, number )`
+- Arguments:
+ - `percent`: Number. The decimal value of the percent (i.e. 100% is 1, 50% is .5).
+ - `number`: Number. The number to get the percentage of.
+- Returns:
+ - `percentage`: Number. The `percent`age or `number`.
+
+##### mlib.math.getPercentOfChange
+- Gets the percent of change from one to another.
+- Synopsis:
+ - `change = mlib.math.getPercentOfChange( old, new )`
+- Arguments:
+ - `old`: Number. The original number.
+ - `new`: Number. The new number.
+- Returns:
+ - `change`: Number. The percent of change from `old` to `new`.
+
+##### mlib.math.getQuadraticRoots
+- Gets the quadratic roots of the the equation.
+- Synopsis:
+ - `root1, root2 = mlib.math.getQuadraticRoots( a, b, c )`
+- Arguments:
+ - `a`, `b`, `c`: Numbers. The a, b, and c values of the equation `a * x ^ 2 + b * x ^ 2 + c`.
+- Returns:
+ - `root1`, `root2`: Numbers. The roots of the equation (where `a * x ^ 2 + b * x ^ 2 + c = 0`).
+
+##### mlib.math.getRoot
+- Gets the `n`th root of a number.
+- Synopsis:
+ - `x = mlib.math.getRoot( number, root )`
+- Arguments:
+ - `number`: Number. The number to get the root of.
+ - `root`: Number. The root.
+- Returns:
+ - `x`: The `root`th root of `number`.
+- Example:
+```lua
+local a = mlib.math.getRoot( 4, 2 ) -- Same as saying 'math.pow( 4, .5 )' or 'math.sqrt( 4 )' in this case.
+local b = mlib.math.getRoot( 27, 3 )
+
+print( a, b ) --> 2, 3
+```
+ - For more, see the [specs](spec.lua# L860).
+
+##### mlib.math.getSummation
+- Gets the summation of numbers.
+- Synopsis:
+ - `summation = mlib.math.getSummation( start, stop, func )`
+- Arguments:
+ - `start`: Number. The number at which to start the summation.
+ - `stop`: Number. The number at which to stop the summation.
+ - `func`: Function. The method to add the numbers.
+ - Arguments:
+ - `i`: Number. Index.
+ - `previous`: Table. The previous values used.
+- Returns:
+ - `Summation`: Number. The summation of the numbers.
+ - For more, see the [specs](spec.lua# L897).
+
+##### mlib.math.isPrime
+- Checks if a number is prime.
+- Synopsis:
+ - `isPrime = mlib.math.isPrime( x )`
+- Arguments:
+ - `x`: Number. The number to check if it's prime.
+- Returns:
+ - `isPrime`: Boolean.
+ - `true` if the number is prime.
+ - `false` if the number is not prime.
+
+##### mlib.math.round
+- Rounds a number to the given decimal place.
+- Synopsis:
+ - `rounded = mlib.math.round( number, [place] )
+- Arguments:
+ - `number`: Number. The number to round.
+ - `place (1)`: Number. The decimal place to round to. Defaults to 1.
+- Returns:
+ - The rounded number.
+ - For more, see the [specs](spec.lua# L881).
+
+#### Aliases
+| Alias | Corresponding Function |
+| ----------------------------------------------|:---------------------------------------------------------------------------------:|
+| milb.line.getDistance | [mlib.line.getLength](#mliblinegetlength) |
+| mlib.line.getCircleIntersection | [mlib.circle.getLineIntersection](#mlibcirclegetlineintersection) |
+| milb.line.getPolygonIntersection | [mlib.polygon.getLineIntersection](#mlibpolygongetlineintersection) |
+| mlib.line.getLineIntersection | [mlib.line.getIntersection](#mliblinegetintersection) |
+| mlib.segment.getCircleIntersection | [mlib.circle.getSegmentIntersection](#mlibcirclegetsegmentintersection) |
+| milb.segment.getPolygonIntersection | [mlib.pollygon.getSegmentIntersection](#mlibpollygongetsegmentintersection) |
+| mlib.segment.getLineIntersection | [mlib.line.getSegmentIntersection](#mliblinegetsegmentintersection) |
+| mlib.segment.getSegmentIntersection | [mlib.segment.getIntersection](#mlibsegmentgetintersection) |
+| milb.segment.isSegmentCompletelyInsideCircle | [mlib.circle.isSegmentCompletelyInside](#mlibcircleissegmentcompletelyinside) |
+| mlib.segment.isSegmentCompletelyInsidePolygon | [mlib.polygon.isSegmentCompletelyInside](#mlibpolygonissegmentcompletelyinside) |
+| mlib.circle.getPolygonIntersection | [mlib.polygon.getCircleIntersection](#mlibpolygongetcircleintersection) |
+| mlib.circle.isCircleInsidePolygon | [mlib.polygon.isCircleInside](#mlibpolygoniscircleinside) |
+| mlib.circle.isCircleCompletelyInsidePolygon | [mlib.polygon.isCircleCompletelyInside](#mlibpolygoniscirclecompletelyinside) |
+| mlib.polygon.isCircleCompletelyOver | [mlib.circleisPolygonCompletelyInside](#mlibcircleispolygoncompletelyinside) |
+
+## License
+A math library made in Lua
+copyright (C) 2014 Davis Claiborne
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+You should have received a copy of the GNU General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+Contact me at davisclaib at gmail.com
diff --git a/libs/windfield/mlib/mlib.lua b/libs/windfield/mlib/mlib.lua
new file mode 100644
index 0000000..488fdb7
--- /dev/null
+++ b/libs/windfield/mlib/mlib.lua
@@ -0,0 +1,1152 @@
+--[[ License
+ A math library made in Lua
+ copyright (C) 2014 Davis Claiborne
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ Contact me at davisclaib@gmail.com
+]]
+
+-- Local Utility Functions ---------------------- {{{
+local unpack = table.unpack or unpack
+
+-- Used to handle variable-argument functions and whether they are passed as func{ table } or func( unpack( table ) )
+local function checkInput( ... )
+ local input = {}
+ if type( ... ) ~= 'table' then input = { ... } else input = ... end
+ return input
+end
+
+-- Deals with floats / verify false false values. This can happen because of significant figures.
+local function checkFuzzy( number1, number2 )
+ return ( number1 - .00001 <= number2 and number2 <= number1 + .00001 )
+end
+
+-- Remove multiple occurrences from a table.
+local function removeDuplicatePairs( tab )
+ for index1 = #tab, 1, -1 do
+ local first = tab[index1]
+ for index2 = #tab, 1, -1 do
+ local second = tab[index2]
+ if index1 ~= index2 then
+ if type( first[1] ) == 'number' and type( second[1] ) == 'number' and type( first[2] ) == 'number' and type( second[2] ) == 'number' then
+ if checkFuzzy( first[1], second[1] ) and checkFuzzy( first[2], second[2] ) then
+ table.remove( tab, index1 )
+ end
+ elseif first[1] == second[1] and first[2] == second[2] then
+ table.remove( tab, index1 )
+ end
+ end
+ end
+ end
+ return tab
+end
+
+
+local function removeDuplicates4Points( tab )
+ for index1 = #tab, 1, -1 do
+ local first = tab[index1]
+ for index2 = #tab, 1, -1 do
+ local second = tab[index2]
+ if index1 ~= index2 then
+ if type( first[1] ) ~= type( second[1] ) then return false end
+ if type( first[2] ) == 'number' and type( second[2] ) == 'number' and type( first[3] ) == 'number' and type( second[3] ) == 'number' then
+ if checkFuzzy( first[2], second[2] ) and checkFuzzy( first[3], second[3] ) then
+ table.remove( tab, index1 )
+ end
+ elseif checkFuzzy( first[1], second[1] ) and checkFuzzy( first[2], second[2] ) and checkFuzzy( first[3], second[3] ) then
+ table.remove( tab, index1 )
+ end
+ end
+ end
+ end
+ return tab
+end
+
+
+-- Add points to the table.
+local function addPoints( tab, x, y )
+ tab[#tab + 1] = x
+ tab[#tab + 1] = y
+end
+
+-- Like removeDuplicatePairs but specifically for numbers in a flat table
+local function removeDuplicatePointsFlat( tab )
+ for i = #tab, 1 -2 do
+ for ii = #tab - 2, 3, -2 do
+ if i ~= ii then
+ local x1, y1 = tab[i], tab[i + 1]
+ local x2, y2 = tab[ii], tab[ii + 1]
+ if checkFuzzy( x1, x2 ) and checkFuzzy( y1, y2 ) then
+ table.remove( tab, ii ); table.remove( tab, ii + 1 )
+ end
+ end
+ end
+ end
+ return tab
+end
+
+
+-- Check if input is actually a number
+local function validateNumber( n )
+ if type( n ) ~= 'number' then return false
+ elseif n ~= n then return false -- nan
+ elseif math.abs( n ) == math.huge then return false
+ else return true end
+end
+
+local function cycle( tab, index ) return tab[( index - 1 ) % #tab + 1] end
+
+local function getGreatestPoint( points, offset )
+ offset = offset or 1
+ local start = 2 - offset
+ local greatest = points[start]
+ local least = points[start]
+ for i = 2, #points / 2 do
+ i = i * 2 - offset
+ if points[i] > greatest then
+ greatest = points[i]
+ end
+ if points[i] < least then
+ least = points[i]
+ end
+ end
+ return greatest, least
+end
+
+local function isWithinBounds( min, num, max )
+ return num >= min and num <= max
+end
+
+local function distance2( x1, y1, x2, y2 ) -- Faster since it does not use math.sqrt
+ local dx, dy = x1 - x2, y1 - y2
+ return dx * dx + dy * dy
+end -- }}}
+
+-- Points -------------------------------------- {{{
+local function rotatePoint( x, y, rotation, ox, oy )
+ ox, oy = ox or 0, oy or 0
+ return ( x - ox ) * math.cos( rotation ) + ox - ( y - oy ) * math.sin( rotation ), ( x - ox ) * math.sin( rotation ) + ( y - oy ) * math.cos( rotation ) + oy
+end
+
+local function scalePoint( x, y, scale, ox, oy )
+ ox, oy = ox or 0, oy or 0
+ return ( x - ox ) * scale + ox, ( y - oy ) * scale + oy
+end
+-- }}}
+
+-- Lines --------------------------------------- {{{
+-- Returns the length of a line.
+local function getLength( x1, y1, x2, y2 )
+ local dx, dy = x1 - x2, y1 - y2
+ return math.sqrt( dx * dx + dy * dy )
+end
+
+-- Gives the midpoint of a line.
+local function getMidpoint( x1, y1, x2, y2 )
+ return ( x1 + x2 ) / 2, ( y1 + y2 ) / 2
+end
+
+-- Gives the slope of a line.
+local function getSlope( x1, y1, x2, y2 )
+ if checkFuzzy( x1, x2 ) then return false end -- Technically it's undefined, but this is easier to program.
+ return ( y1 - y2 ) / ( x1 - x2 )
+end
+
+-- Gives the perpendicular slope of a line.
+-- x1, y1, x2, y2
+-- slope
+local function getPerpendicularSlope( ... )
+ local input = checkInput( ... )
+ local slope
+
+ if #input ~= 1 then
+ slope = getSlope( unpack( input ) )
+ else
+ slope = unpack( input )
+ end
+
+ if not slope then return 0 -- Vertical lines become horizontal.
+ elseif checkFuzzy( slope, 0 ) then return false -- Horizontal lines become vertical.
+ else return -1 / slope end
+end
+
+-- Gives the y-intercept of a line.
+-- x1, y1, x2, y2
+-- x1, y1, slope
+local function getYIntercept( x, y, ... )
+ local input = checkInput( ... )
+ local slope
+
+ if #input == 1 then
+ slope = input[1]
+ else
+ slope = getSlope( x, y, unpack( input ) )
+ end
+
+ if not slope then return x, true end -- This way we have some information on the line.
+ return y - slope * x, false
+end
+
+-- Gives the intersection of two lines.
+-- slope1, slope2, x1, y1, x2, y2
+-- slope1, intercept1, slope2, intercept2
+-- x1, y1, x2, y2, x3, y3, x4, y4
+local function getLineLineIntersection( ... )
+ local input = checkInput( ... )
+ local x1, y1, x2, y2, x3, y3, x4, y4
+ local slope1, intercept1
+ local slope2, intercept2
+ local x, y
+
+ if #input == 4 then -- Given slope1, intercept1, slope2, intercept2.
+ slope1, intercept1, slope2, intercept2 = unpack( input )
+
+ -- Since these are lines, not segments, we can use arbitrary points, such as ( 1, y ), ( 2, y )
+ y1 = slope1 and slope1 * 1 + intercept1 or 1
+ y2 = slope1 and slope1 * 2 + intercept1 or 2
+ y3 = slope2 and slope2 * 1 + intercept2 or 1
+ y4 = slope2 and slope2 * 2 + intercept2 or 2
+ x1 = slope1 and ( y1 - intercept1 ) / slope1 or intercept1
+ x2 = slope1 and ( y2 - intercept1 ) / slope1 or intercept1
+ x3 = slope2 and ( y3 - intercept2 ) / slope2 or intercept2
+ x4 = slope2 and ( y4 - intercept2 ) / slope2 or intercept2
+ elseif #input == 6 then -- Given slope1, intercept1, and 2 points on the other line.
+ slope1, intercept1 = input[1], input[2]
+ slope2 = getSlope( input[3], input[4], input[5], input[6] )
+ intercept2 = getYIntercept( input[3], input[4], input[5], input[6] )
+
+ y1 = slope1 and slope1 * 1 + intercept1 or 1
+ y2 = slope1 and slope1 * 2 + intercept1 or 2
+ y3 = input[4]
+ y4 = input[6]
+ x1 = slope1 and ( y1 - intercept1 ) / slope1 or intercept1
+ x2 = slope1 and ( y2 - intercept1 ) / slope1 or intercept1
+ x3 = input[3]
+ x4 = input[5]
+ elseif #input == 8 then -- Given 2 points on line 1 and 2 points on line 2.
+ slope1 = getSlope( input[1], input[2], input[3], input[4] )
+ intercept1 = getYIntercept( input[1], input[2], input[3], input[4] )
+ slope2 = getSlope( input[5], input[6], input[7], input[8] )
+ intercept2 = getYIntercept( input[5], input[6], input[7], input[8] )
+
+ x1, y1, x2, y2, x3, y3, x4, y4 = unpack( input )
+ end
+
+ if not slope1 and not slope2 then -- Both are vertical lines
+ if x1 == x3 then -- Have to have the same x positions to intersect
+ return true
+ else
+ return false
+ end
+ elseif not slope1 then -- First is vertical
+ x = x1 -- They have to meet at this x, since it is this line's only x
+ y = slope2 and slope2 * x + intercept2 or 1
+ elseif not slope2 then -- Second is vertical
+ x = x3 -- Vice-Versa
+ y = slope1 * x + intercept1
+ elseif checkFuzzy( slope1, slope2 ) then -- Parallel (not vertical)
+ if checkFuzzy( intercept1, intercept2 ) then -- Same intercept
+ return true
+ else
+ return false
+ end
+ else -- Regular lines
+ x = ( -intercept1 + intercept2 ) / ( slope1 - slope2 )
+ y = slope1 * x + intercept1
+ end
+
+ return x, y
+end
+
+-- Gives the closest point on a line to a point.
+-- perpendicularX, perpendicularY, x1, y1, x2, y2
+-- perpendicularX, perpendicularY, slope, intercept
+local function getClosestPoint( perpendicularX, perpendicularY, ... )
+ local input = checkInput( ... )
+ local x, y, x1, y1, x2, y2, slope, intercept
+
+ if #input == 4 then -- Given perpendicularX, perpendicularY, x1, y1, x2, y2
+ x1, y1, x2, y2 = unpack( input )
+ slope = getSlope( x1, y1, x2, y2 )
+ intercept = getYIntercept( x1, y1, x2, y2 )
+ elseif #input == 2 then -- Given perpendicularX, perpendicularY, slope, intercept
+ slope, intercept = unpack( input )
+ x1, y1 = 1, slope and slope * 1 + intercept or 1 -- Need x1 and y1 in case of vertical/horizontal lines.
+ end
+
+ if not slope then -- Vertical line
+ x, y = x1, perpendicularY -- Closest point is always perpendicular.
+ elseif checkFuzzy( slope, 0 ) then -- Horizontal line
+ x, y = perpendicularX, y1
+ else
+ local perpendicularSlope = getPerpendicularSlope( slope )
+ local perpendicularIntercept = getYIntercept( perpendicularX, perpendicularY, perpendicularSlope )
+ x, y = getLineLineIntersection( slope, intercept, perpendicularSlope, perpendicularIntercept )
+ end
+
+ return x, y
+end
+
+-- Gives the intersection of a line and a line segment.
+-- x1, y1, x2, y2, x3, y3, x4, y4
+-- x1, y1, x2, y2, slope, intercept
+local function getLineSegmentIntersection( x1, y1, x2, y2, ... )
+ local input = checkInput( ... )
+
+ local slope1, intercept1, x, y, lineX1, lineY1, lineX2, lineY2
+ local slope2, intercept2 = getSlope( x1, y1, x2, y2 ), getYIntercept( x1, y1, x2, y2 )
+
+ if #input == 2 then -- Given slope, intercept
+ slope1, intercept1 = input[1], input[2]
+ lineX1, lineY1 = 1, slope1 and slope1 + intercept1
+ lineX2, lineY2 = 2, slope1 and slope1 * 2 + intercept1
+ else -- Given x3, y3, x4, y4
+ lineX1, lineY1, lineX2, lineY2 = unpack( input )
+ slope1 = getSlope( unpack( input ) )
+ intercept1 = getYIntercept( unpack( input ) )
+ end
+
+ if not slope1 and not slope2 then -- Vertical lines
+ if checkFuzzy( x1, lineX1 ) then
+ return x1, y1, x2, y2
+ else
+ return false
+ end
+ elseif not slope1 then -- slope1 is vertical
+ x, y = input[1], slope2 * input[1] + intercept2
+ elseif not slope2 then -- slope2 is vertical
+ x, y = x1, slope1 * x1 + intercept1
+ else
+ x, y = getLineLineIntersection( slope1, intercept1, slope2, intercept2 )
+ end
+
+ local length1, length2, distance
+ if x == true then -- Lines are collinear.
+ return x1, y1, x2, y2
+ elseif x then -- There is an intersection
+ length1, length2 = getLength( x1, y1, x, y ), getLength( x2, y2, x, y )
+ distance = getLength( x1, y1, x2, y2 )
+ else -- Lines are parallel but not collinear.
+ if checkFuzzy( intercept1, intercept2 ) then
+ return x1, y1, x2, y2
+ else
+ return false
+ end
+ end
+
+ if length1 <= distance and length2 <= distance then return x, y else return false end
+end
+
+-- Checks if a point is on a line.
+-- Does not support the format using slope because vertical lines would be impossible to check.
+local function checkLinePoint( x, y, x1, y1, x2, y2 )
+ local m = getSlope( x1, y1, x2, y2 )
+ local b = getYIntercept( x1, y1, m )
+
+ if not m then -- Vertical
+ return checkFuzzy( x, x1 )
+ end
+ return checkFuzzy( y, m * x + b )
+end -- }}}
+
+-- Segment -------------------------------------- {{{
+-- Gives the perpendicular bisector of a line.
+local function getPerpendicularBisector( x1, y1, x2, y2 )
+ local slope = getSlope( x1, y1, x2, y2 )
+ local midpointX, midpointY = getMidpoint( x1, y1, x2, y2 )
+ return midpointX, midpointY, getPerpendicularSlope( slope )
+end
+
+-- Gives whether or not a point lies on a line segment.
+local function checkSegmentPoint( px, py, x1, y1, x2, y2 )
+ -- Explanation around 5:20: https://www.youtube.com/watch?v=A86COO8KC58
+ local x = checkLinePoint( px, py, x1, y1, x2, y2 )
+ if not x then return false end
+
+ local lengthX = x2 - x1
+ local lengthY = y2 - y1
+
+ if checkFuzzy( lengthX, 0 ) then -- Vertical line
+ if checkFuzzy( px, x1 ) then
+ local low, high
+ if y1 > y2 then low = y2; high = y1
+ else low = y1; high = y2 end
+
+ if py >= low and py <= high then return true
+ else return false end
+ else
+ return false
+ end
+ elseif checkFuzzy( lengthY, 0 ) then -- Horizontal line
+ if checkFuzzy( py, y1 ) then
+ local low, high
+ if x1 > x2 then low = x2; high = x1
+ else low = x1; high = x2 end
+
+ if px >= low and px <= high then return true
+ else return false end
+ else
+ return false
+ end
+ end
+
+ local distanceToPointX = ( px - x1 )
+ local distanceToPointY = ( py - y1 )
+ local scaleX = distanceToPointX / lengthX
+ local scaleY = distanceToPointY / lengthY
+
+ if ( scaleX >= 0 and scaleX <= 1 ) and ( scaleY >= 0 and scaleY <= 1 ) then -- Intersection
+ return true
+ end
+ return false
+end
+
+-- Gives the point of intersection between two line segments.
+local function getSegmentSegmentIntersection( x1, y1, x2, y2, x3, y3, x4, y4 )
+ local slope1, intercept1 = getSlope( x1, y1, x2, y2 ), getYIntercept( x1, y1, x2, y2 )
+ local slope2, intercept2 = getSlope( x3, y3, x4, y4 ), getYIntercept( x3, y3, x4, y4 )
+
+ if ( ( slope1 and slope2 ) and checkFuzzy( slope1, slope2 ) ) or ( not slope1 and not slope2 ) then -- Parallel lines
+ if checkFuzzy( intercept1, intercept2 ) then -- The same lines, possibly in different points.
+ local points = {}
+ if checkSegmentPoint( x1, y1, x3, y3, x4, y4 ) then addPoints( points, x1, y1 ) end
+ if checkSegmentPoint( x2, y2, x3, y3, x4, y4 ) then addPoints( points, x2, y2 ) end
+ if checkSegmentPoint( x3, y3, x1, y1, x2, y2 ) then addPoints( points, x3, y3 ) end
+ if checkSegmentPoint( x4, y4, x1, y1, x2, y2 ) then addPoints( points, x4, y4 ) end
+
+ points = removeDuplicatePointsFlat( points )
+ if #points == 0 then return false end
+ return unpack( points )
+ else
+ return false
+ end
+ end
+
+ local x, y = getLineLineIntersection( x1, y1, x2, y2, x3, y3, x4, y4 )
+ if x and checkSegmentPoint( x, y, x1, y1, x2, y2 ) and checkSegmentPoint( x, y, x3, y3, x4, y4 ) then
+ return x, y
+ end
+ return false
+end -- }}}
+
+-- Math ----------------------------------------- {{{
+-- Get the root of a number (i.e. the 2nd (square) root of 4 is 2)
+local function getRoot( number, root )
+ return number ^ ( 1 / root )
+end
+
+-- Checks if a number is prime.
+local function isPrime( number )
+ if number < 2 then return false end
+
+ for i = 2, math.sqrt( number ) do
+ if number % i == 0 then
+ return false
+ end
+ end
+ return true
+end
+
+-- Rounds a number to the xth decimal place (round( 3.14159265359, 4 ) --> 3.1416)
+local function round( number, place )
+ local pow = 10 ^ ( place or 0 )
+ return math.floor( number * pow + .5 ) / pow
+end
+
+-- Gives the summation given a local function
+local function getSummation( start, stop, func )
+ local returnValues = {}
+ local sum = 0
+ for i = start, stop do
+ local value = func( i, returnValues )
+ returnValues[i] = value
+ sum = sum + value
+ end
+ return sum
+end
+
+-- Gives the percent of change.
+local function getPercentOfChange( old, new )
+ if old == 0 and new == 0 then
+ return 0
+ else
+ return ( new - old ) / math.abs( old )
+ end
+end
+
+-- Gives the percentage of a number.
+local function getPercentage( percent, number )
+ return percent * number
+end
+
+-- Returns the quadratic roots of an equation.
+local function getQuadraticRoots( a, b, c )
+ local discriminant = b ^ 2 - ( 4 * a * c )
+ if discriminant < 0 then return false end
+ discriminant = math.sqrt( discriminant )
+ local denominator = ( 2 * a )
+ return ( -b - discriminant ) / denominator, ( -b + discriminant ) / denominator
+end
+
+-- Gives the angle between three points.
+local function getAngle( x1, y1, x2, y2, x3, y3 )
+ local a = getLength( x3, y3, x2, y2 )
+ local b = getLength( x1, y1, x2, y2 )
+ local c = getLength( x1, y1, x3, y3 )
+
+ return math.acos( ( a * a + b * b - c * c ) / ( 2 * a * b ) )
+end -- }}}
+
+-- Circle --------------------------------------- {{{
+-- Gives the area of the circle.
+local function getCircleArea( radius )
+ return math.pi * ( radius * radius )
+end
+
+-- Checks if a point is within the radius of a circle.
+local function checkCirclePoint( x, y, circleX, circleY, radius )
+ return getLength( circleX, circleY, x, y ) <= radius
+end
+
+-- Checks if a point is on a circle.
+local function isPointOnCircle( x, y, circleX, circleY, radius )
+ return checkFuzzy( getLength( circleX, circleY, x, y ), radius )
+end
+
+-- Gives the circumference of a circle.
+local function getCircumference( radius )
+ return 2 * math.pi * radius
+end
+
+-- Gives the intersection of a line and a circle.
+local function getCircleLineIntersection( circleX, circleY, radius, x1, y1, x2, y2 )
+ slope = getSlope( x1, y1, x2, y2 )
+ intercept = getYIntercept( x1, y1, slope )
+
+ if slope then
+ local a = ( 1 + slope ^ 2 )
+ local b = ( -2 * ( circleX ) + ( 2 * slope * intercept ) - ( 2 * circleY * slope ) )
+ local c = ( circleX ^ 2 + intercept ^ 2 - 2 * ( circleY ) * ( intercept ) + circleY ^ 2 - radius ^ 2 )
+
+ x1, x2 = getQuadraticRoots( a, b, c )
+
+ if not x1 then return false end
+
+ y1 = slope * x1 + intercept
+ y2 = slope * x2 + intercept
+
+ if checkFuzzy( x1, x2 ) and checkFuzzy( y1, y2 ) then
+ return 'tangent', x1, y1
+ else
+ return 'secant', x1, y1, x2, y2
+ end
+ else -- Vertical Lines
+ local lengthToPoint1 = circleX - x1
+ local remainingDistance = lengthToPoint1 - radius
+ local intercept = math.sqrt( -( lengthToPoint1 ^ 2 - radius ^ 2 ) )
+
+ if -( lengthToPoint1 ^ 2 - radius ^ 2 ) < 0 then return false end
+
+ local bottomX, bottomY = x1, circleY - intercept
+ local topX, topY = x1, circleY + intercept
+
+ if topY ~= bottomY then
+ return 'secant', topX, topY, bottomX, bottomY
+ else
+ return 'tangent', topX, topY
+ end
+ end
+end
+
+-- Gives the type of intersection of a line segment.
+local function getCircleSegmentIntersection( circleX, circleY, radius, x1, y1, x2, y2 )
+ local Type, x3, y3, x4, y4 = getCircleLineIntersection( circleX, circleY, radius, x1, y1, x2, y2 )
+ if not Type then return false end
+
+ local slope, intercept = getSlope( x1, y1, x2, y2 ), getYIntercept( x1, y1, x2, y2 )
+
+ if isPointOnCircle( x1, y1, circleX, circleY, radius ) and isPointOnCircle( x2, y2, circleX, circleY, radius ) then -- Both points are on line-segment.
+ return 'chord', x1, y1, x2, y2
+ end
+
+ if slope then
+ if checkCirclePoint( x1, y1, circleX, circleY, radius ) and checkCirclePoint( x2, y2, circleX, circleY, radius ) then -- Line-segment is fully in circle.
+ return 'enclosed', x1, y1, x2, y2
+ elseif x3 and x4 then
+ 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.
+ return 'tangent', x3, y3
+ 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.
+ return 'tangent', x4, y4
+ else -- Neither of the points are on the circle (means that the segment is not on the circle, but "encasing" the circle)
+ if checkSegmentPoint( x3, y3, x1, y1, x2, y2 ) and checkSegmentPoint( x4, y4, x1, y1, x2, y2 ) then
+ return 'secant', x3, y3, x4, y4
+ else
+ return false
+ end
+ end
+ elseif not x4 then -- Is a tangent.
+ if checkSegmentPoint( x3, y3, x1, y1, x2, y2 ) then
+ return 'tangent', x3, y3
+ else -- Neither of the points are on the line-segment (means that the segment is not on the circle or "encasing" the circle).
+ local length = getLength( x1, y1, x2, y2 )
+ local distance1 = getLength( x1, y1, x3, y3 )
+ local distance2 = getLength( x2, y2, x3, y3 )
+
+ if length > distance1 or length > distance2 then
+ return false
+ elseif length < distance1 and length < distance2 then
+ return false
+ else
+ return 'tangent', x3, y3
+ end
+ end
+ end
+ else
+ local lengthToPoint1 = circleX - x1
+ local remainingDistance = lengthToPoint1 - radius
+ local intercept = math.sqrt( -( lengthToPoint1 ^ 2 - radius ^ 2 ) )
+
+ if -( lengthToPoint1 ^ 2 - radius ^ 2 ) < 0 then return false end
+
+ local topX, topY = x1, circleY - intercept
+ local bottomX, bottomY = x1, circleY + intercept
+
+ local length = getLength( x1, y1, x2, y2 )
+ local distance1 = getLength( x1, y1, topX, topY )
+ local distance2 = getLength( x2, y2, topX, topY )
+
+ if bottomY ~= topY then -- Not a tangent
+ if checkSegmentPoint( topX, topY, x1, y1, x2, y2 ) and checkSegmentPoint( bottomX, bottomY, x1, y1, x2, y2 ) then
+ return 'chord', topX, topY, bottomX, bottomY
+ elseif checkSegmentPoint( topX, topY, x1, y1, x2, y2 ) then
+ return 'tangent', topX, topY
+ elseif checkSegmentPoint( bottomX, bottomY, x1, y1, x2, y2 ) then
+ return 'tangent', bottomX, bottomY
+ else
+ return false
+ end
+ else -- Tangent
+ if checkSegmentPoint( topX, topY, x1, y1, x2, y2 ) then
+ return 'tangent', topX, topY
+ else
+ return false
+ end
+ end
+ end
+end
+
+-- Checks if one circle intersects another circle.
+local function getCircleCircleIntersection( circle1x, circle1y, radius1, circle2x, circle2y, radius2 )
+ local length = getLength( circle1x, circle1y, circle2x, circle2y )
+ if length > radius1 + radius2 then return false end -- If the distance is greater than the two radii, they can't intersect.
+ if checkFuzzy( length, 0 ) and checkFuzzy( radius1, radius2 ) then return 'equal' end
+ if checkFuzzy( circle1x, circle2x ) and checkFuzzy( circle1y, circle2y ) then return 'collinear' end
+
+ local a = ( radius1 * radius1 - radius2 * radius2 + length * length ) / ( 2 * length )
+ local h = math.sqrt( radius1 * radius1 - a * a )
+
+ local p2x = circle1x + a * ( circle2x - circle1x ) / length
+ local p2y = circle1y + a * ( circle2y - circle1y ) / length
+ local p3x = p2x + h * ( circle2y - circle1y ) / length
+ local p3y = p2y - h * ( circle2x - circle1x ) / length
+ local p4x = p2x - h * ( circle2y - circle1y ) / length
+ local p4y = p2y + h * ( circle2x - circle1x ) / length
+
+ if not validateNumber( p3x ) or not validateNumber( p3y ) or not validateNumber( p4x ) or not validateNumber( p4y ) then
+ return 'inside'
+ end
+
+ if checkFuzzy( length, radius1 + radius2 ) or checkFuzzy( length, math.abs( radius1 - radius2 ) ) then return 'tangent', p3x, p3y end
+ return 'intersection', p3x, p3y, p4x, p4y
+end
+
+-- Checks if circle1 is entirely inside of circle2.
+local function isCircleCompletelyInsideCircle( circle1x, circle1y, circle1radius, circle2x, circle2y, circle2radius )
+ if not checkCirclePoint( circle1x, circle1y, circle2x, circle2y, circle2radius ) then return false end
+ local Type = getCircleCircleIntersection( circle2x, circle2y, circle2radius, circle1x, circle1y, circle1radius )
+ if ( Type ~= 'tangent' and Type ~= 'collinear' and Type ~= 'inside' ) then return false end
+ return true
+end
+
+-- Checks if a line-segment is entirely within a circle.
+local function isSegmentCompletelyInsideCircle( circleX, circleY, circleRadius, x1, y1, x2, y2 )
+ local Type = getCircleSegmentIntersection( circleX, circleY, circleRadius, x1, y1, x2, y2 )
+ return Type == 'enclosed'
+end -- }}}
+
+-- Polygon -------------------------------------- {{{
+-- Gives the signed area.
+-- If the points are clockwise the number is negative, otherwise, it's positive.
+local function getSignedPolygonArea( ... )
+ local points = checkInput( ... )
+
+ -- Shoelace formula (https://en.wikipedia.org/wiki/Shoelace_formula).
+ points[#points + 1] = points[1]
+ points[#points + 1] = points[2]
+
+ return ( .5 * getSummation( 1, #points / 2,
+ function( index )
+ index = index * 2 - 1 -- Convert it to work properly.
+ return ( ( points[index] * cycle( points, index + 3 ) ) - ( cycle( points, index + 2 ) * points[index + 1] ) )
+ end
+ ) )
+end
+
+-- Simply returns the area of the polygon.
+local function getPolygonArea( ... )
+ return math.abs( getSignedPolygonArea( ... ) )
+end
+
+-- Gives the height of a triangle, given the base.
+-- base, x1, y1, x2, y2, x3, y3, x4, y4
+-- base, area
+local function getTriangleHeight( base, ... )
+ local input = checkInput( ... )
+ local area
+
+ if #input == 1 then area = input[1] -- Given area.
+ else area = getPolygonArea( input ) end -- Given coordinates.
+
+ return ( 2 * area ) / base, area
+end
+
+-- Gives the centroid of the polygon.
+local function getCentroid( ... )
+ local points = checkInput( ... )
+
+ points[#points + 1] = points[1]
+ points[#points + 1] = points[2]
+
+ local area = getSignedPolygonArea( points ) -- Needs to be signed here in case points are counter-clockwise.
+
+ -- This formula: https://en.wikipedia.org/wiki/Centroid#Centroid_of_polygon
+ local centroidX = ( 1 / ( 6 * area ) ) * ( getSummation( 1, #points / 2,
+ function( index )
+ index = index * 2 - 1 -- Convert it to work properly.
+ return ( ( points[index] + cycle( points, index + 2 ) ) * ( ( points[index] * cycle( points, index + 3 ) ) - ( cycle( points, index + 2 ) * points[index + 1] ) ) )
+ end
+ ) )
+
+ local centroidY = ( 1 / ( 6 * area ) ) * ( getSummation( 1, #points / 2,
+ function( index )
+ index = index * 2 - 1 -- Convert it to work properly.
+ return ( ( points[index + 1] + cycle( points, index + 3 ) ) * ( ( points[index] * cycle( points, index + 3 ) ) - ( cycle( points, index + 2 ) * points[index + 1] ) ) )
+ end
+ ) )
+
+ return centroidX, centroidY
+end
+
+-- Returns whether or not a line intersects a polygon.
+-- x1, y1, x2, y2, polygonPoints
+local function getPolygonLineIntersection( x1, y1, x2, y2, ... )
+ local input = checkInput( ... )
+ local choices = {}
+
+ local slope = getSlope( x1, y1, x2, y2 )
+ local intercept = getYIntercept( x1, y1, slope )
+
+ local x3, y3, x4, y4
+ if slope then
+ x3, x4 = 1, 2
+ y3, y4 = slope * x3 + intercept, slope * x4 + intercept
+ else
+ x3, x4 = x1, x1
+ y3, y4 = y1, y2
+ end
+
+ for i = 1, #input, 2 do
+ local x1, y1, x2, y2 = getLineSegmentIntersection( input[i], input[i + 1], cycle( input, i + 2 ), cycle( input, i + 3 ), x3, y3, x4, y4 )
+ if x1 and not x2 then choices[#choices + 1] = { x1, y1 }
+ elseif x1 and x2 then choices[#choices + 1] = { x1, y1, x2, y2 } end
+ -- No need to check 2-point sets since they only intersect each poly line once.
+ end
+
+ local final = removeDuplicatePairs( choices )
+ return #final > 0 and final or false
+end
+
+-- Returns if the line segment intersects the polygon.
+-- x1, y1, x2, y2, polygonPoints
+local function getPolygonSegmentIntersection( x1, y1, x2, y2, ... )
+ local input = checkInput( ... )
+ local choices = {}
+
+ for i = 1, #input, 2 do
+ local x1, y1, x2, y2 = getSegmentSegmentIntersection( input[i], input[i + 1], cycle( input, i + 2 ), cycle( input, i + 3 ), x1, y1, x2, y2 )
+ if x1 and not x2 then choices[#choices + 1] = { x1, y1 }
+ elseif x2 then choices[#choices + 1] = { x1, y1, x2, y2 } end
+ end
+
+ local final = removeDuplicatePairs( choices )
+ return #final > 0 and final or false
+end
+
+-- Checks if the point lies INSIDE the polygon not on the polygon.
+local function checkPolygonPoint( px, py, ... )
+ local points = { unpack( checkInput( ... ) ) } -- Make a new table, as to not edit values of previous.
+
+ local greatest, least = getGreatestPoint( points, 0 )
+ if not isWithinBounds( least, py, greatest ) then return false end
+ greatest, least = getGreatestPoint( points )
+ if not isWithinBounds( least, px, greatest ) then return false end
+
+ local count = 0
+ for i = 1, #points, 2 do
+ if checkFuzzy( points[i + 1], py ) then
+ points[i + 1] = py + .001 -- Handles vertices that lie on the point.
+ -- Not exactly mathematically correct, but a lot easier.
+ end
+ if points[i + 3] and checkFuzzy( points[i + 3], py ) then
+ points[i + 3] = py + .001 -- Do not need to worry about alternate case, since points[2] has already been done.
+ end
+ local x1, y1 = points[i], points[i + 1]
+ local x2, y2 = points[i + 2] or points[1], points[i + 3] or points[2]
+
+ if getSegmentSegmentIntersection( px, py, greatest, py, x1, y1, x2, y2 ) then
+ count = count + 1
+ end
+ end
+
+ return count and count % 2 ~= 0
+end
+
+-- Returns if the line segment is fully or partially inside.
+-- x1, y1, x2, y2, polygonPoints
+local function isSegmentInsidePolygon( x1, y1, x2, y2, ... )
+ local input = checkInput( ... )
+
+ local choices = getPolygonSegmentIntersection( x1, y1, x2, y2, input ) -- If it's partially enclosed that's all we need.
+ if choices then return true end
+
+ if checkPolygonPoint( x1, y1, input ) or checkPolygonPoint( x2, y2, input ) then return true end
+ return false
+end
+
+-- Returns whether two polygons intersect.
+local function getPolygonPolygonIntersection( polygon1, polygon2 )
+ local choices = {}
+
+ for index1 = 1, #polygon1, 2 do
+ local intersections = getPolygonSegmentIntersection( polygon1[index1], polygon1[index1 + 1], cycle( polygon1, index1 + 2 ), cycle( polygon1, index1 + 3 ), polygon2 )
+ if intersections then
+ for index2 = 1, #intersections do
+ choices[#choices + 1] = intersections[index2]
+ end
+ end
+ end
+
+ for index1 = 1, #polygon2, 2 do
+ local intersections = getPolygonSegmentIntersection( polygon2[index1], polygon2[index1 + 1], cycle( polygon2, index1 + 2 ), cycle( polygon2, index1 + 3 ), polygon1 )
+ if intersections then
+ for index2 = 1, #intersections do
+ choices[#choices + 1] = intersections[index2]
+ end
+ end
+ end
+
+ choices = removeDuplicatePairs( choices )
+ for i = #choices, 1, -1 do
+ if type( choices[i][1] ) == 'table' then -- Remove co-linear pairs.
+ table.remove( choices, i )
+ end
+ end
+
+ return #choices > 0 and choices
+end
+
+-- Returns whether the circle intersects the polygon.
+-- x, y, radius, polygonPoints
+local function getPolygonCircleIntersection( x, y, radius, ... )
+ local input = checkInput( ... )
+ local choices = {}
+
+ for i = 1, #input, 2 do
+ local Type, x1, y1, x2, y2 = getCircleSegmentIntersection( x, y, radius, input[i], input[i + 1], cycle( input, i + 2 ), cycle( input, i + 3 ) )
+ if x2 then
+ choices[#choices + 1] = { Type, x1, y1, x2, y2 }
+ elseif x1 then choices[#choices + 1] = { Type, x1, y1 } end
+ end
+
+ local final = removeDuplicates4Points( choices )
+
+ return #final > 0 and final
+end
+
+-- Returns whether the circle is inside the polygon.
+-- x, y, radius, polygonPoints
+local function isCircleInsidePolygon( x, y, radius, ... )
+ local input = checkInput( ... )
+ return checkPolygonPoint( x, y, input )
+end
+
+-- Returns whether the polygon is inside the polygon.
+local function isPolygonInsidePolygon( polygon1, polygon2 )
+ local bool = false
+ for i = 1, #polygon2, 2 do
+ local result = false
+ result = isSegmentInsidePolygon( polygon2[i], polygon2[i + 1], cycle( polygon2, i + 2 ), cycle( polygon2, i + 3 ), polygon1 )
+ if result then bool = true; break end
+ end
+ return bool
+end
+
+-- Checks if a segment is completely inside a polygon
+local function isSegmentCompletelyInsidePolygon( x1, y1, x2, y2, ... )
+ local polygon = checkInput( ... )
+ if not checkPolygonPoint( x1, y1, polygon )
+ or not checkPolygonPoint( x2, y2, polygon )
+ or getPolygonSegmentIntersection( x1, y1, x2, y2, polygon ) then
+ return false
+ end
+ return true
+end
+
+-- Checks if a polygon is completely inside another polygon
+local function isPolygonCompletelyInsidePolygon( polygon1, polygon2 )
+ for i = 1, #polygon1, 2 do
+ local x1, y1 = polygon1[i], polygon1[i + 1]
+ local x2, y2 = polygon1[i + 2] or polygon1[1], polygon1[i + 3] or polygon1[2]
+ if not isSegmentCompletelyInsidePolygon( x1, y1, x2, y2, polygon2 ) then
+ return false
+ end
+ end
+ return true
+end
+
+-------------- Circle w/ Polygons --------------
+-- Gets if a polygon is completely within a circle
+-- circleX, circleY, circleRadius, polygonPoints
+local function isPolygonCompletelyInsideCircle( circleX, circleY, circleRadius, ... )
+ local input = checkInput( ... )
+ local function isDistanceLess( px, py, x, y, circleRadius ) -- Faster, does not use math.sqrt
+ local distanceX, distanceY = px - x, py - y
+ return distanceX * distanceX + distanceY * distanceY < circleRadius * circleRadius -- Faster. For comparing distances only.
+ end
+
+ for i = 1, #input, 2 do
+ if not checkCirclePoint( input[i], input[i + 1], circleX, circleY, circleRadius ) then return false end
+ end
+ return true
+end
+
+-- Checks if a circle is completely within a polygon
+-- circleX, circleY, circleRadius, polygonPoints
+local function isCircleCompletelyInsidePolygon( circleX, circleY, circleRadius, ... )
+ local input = checkInput( ... )
+ if not checkPolygonPoint( circleX, circleY, ... ) then return false end
+
+ local rad2 = circleRadius * circleRadius
+
+ for i = 1, #input, 2 do
+ local x1, y1 = input[i], input[i + 1]
+ local x2, y2 = input[i + 2] or input[1], input[i + 3] or input[2]
+ if distance2( x1, y1, circleX, circleY ) <= rad2 then return false end
+ if getCircleSegmentIntersection( circleX, circleY, circleRadius, x1, y1, x2, y2 ) then return false end
+ end
+ return true
+end -- }}}
+
+-- Statistics ----------------------------------- {{{
+-- Gets the average of a list of points
+-- points
+local function getMean( ... )
+ local input = checkInput( ... )
+
+ mean = getSummation( 1, #input,
+ function( i, t )
+ return input[i]
+ end
+ ) / #input
+
+ return mean
+end
+
+local function getMedian( ... )
+ local input = checkInput( ... )
+
+ table.sort( input )
+
+ local median
+ if #input % 2 == 0 then -- If you have an even number of terms, you need to get the average of the middle 2.
+ median = getMean( input[#input / 2], input[#input / 2 + 1] )
+ else
+ median = input[#input / 2 + .5]
+ end
+
+ return median
+end
+
+-- Gets the mode of a number.
+local function getMode( ... )
+ local input = checkInput( ... )
+
+ table.sort( input )
+ local sorted = {}
+ for i = 1, #input do
+ local value = input[i]
+ sorted[value] = sorted[value] and sorted[value] + 1 or 1
+ end
+
+ local occurrences, least = 0, {}
+ for i, value in pairs( sorted ) do
+ if value > occurrences then
+ least = { i }
+ occurrences = value
+ elseif value == occurrences then
+ least[#least + 1] = i
+ end
+ end
+
+ if #least >= 1 then return least, occurrences
+ else return false end
+end
+
+-- Gets the range of the numbers.
+local function getRange( ... )
+ local input = checkInput( ... )
+ local high, low = math.max( unpack( input ) ), math.min( unpack( input ) )
+ return high - low
+end
+
+-- Gets the variance of a set of numbers.
+local function getVariance( ... )
+ local input = checkInput( ... )
+ local mean = getMean( ... )
+ local sum = 0
+ for i = 1, #input do
+ sum = sum + ( mean - input[i] ) * ( mean - input[i] )
+ end
+ return sum / #input
+end
+
+-- Gets the standard deviation of a set of numbers.
+local function getStandardDeviation( ... )
+ return math.sqrt( getVariance( ... ) )
+end
+
+-- Gets the central tendency of a set of numbers.
+local function getCentralTendency( ... )
+ local mode, occurrences = getMode( ... )
+ return mode, occurrences, getMedian( ... ), getMean( ... )
+end
+
+-- Gets the variation ratio of a data set.
+local function getVariationRatio( ... )
+ local input = checkInput( ... )
+ local numbers, times = getMode( ... )
+ times = times * #numbers -- Account for bimodal data
+ return 1 - ( times / #input )
+end
+
+-- Gets the measures of dispersion of a data set.
+local function getDispersion( ... )
+ return getVariationRatio( ... ), getRange( ... ), getStandardDeviation( ... )
+end -- }}}
+
+return {
+ _VERSION = 'MLib 0.10.0',
+ _DESCRIPTION = 'A math and shape-intersection detection library for Lua',
+ _URL = 'https://github.com/davisdude/mlib',
+ point = {
+ rotate = rotatePoint,
+ scale = scalePoint,
+ },
+ line = {
+ getLength = getLength,
+ getMidpoint = getMidpoint,
+ getSlope = getSlope,
+ getPerpendicularSlope = getPerpendicularSlope,
+ getYIntercept = getYIntercept,
+ getIntersection = getLineLineIntersection,
+ getClosestPoint = getClosestPoint,
+ getSegmentIntersection = getLineSegmentIntersection,
+ checkPoint = checkLinePoint,
+
+ -- Aliases
+ getDistance = getLength,
+ getCircleIntersection = getCircleLineIntersection,
+ getPolygonIntersection = getPolygonLineIntersection,
+ getLineIntersection = getLineLineIntersection,
+ },
+ segment = {
+ checkPoint = checkSegmentPoint,
+ getPerpendicularBisector = getPerpendicularBisector,
+ getIntersection = getSegmentSegmentIntersection,
+
+ -- Aliases
+ getCircleIntersection = getCircleSegmentIntersection,
+ getPolygonIntersection = getPolygonSegmentIntersection,
+ getLineIntersection = getLineSegmentIntersection,
+ getSegmentIntersection = getSegmentSegmentIntersection,
+ isSegmentCompletelyInsideCircle = isSegmentCompletelyInsideCircle,
+ isSegmentCompletelyInsidePolygon = isSegmentCompletelyInsidePolygon,
+ },
+ math = {
+ getRoot = getRoot,
+ isPrime = isPrime,
+ round = round,
+ getSummation = getSummation,
+ getPercentOfChange = getPercentOfChange,
+ getPercentage = getPercentage,
+ getQuadraticRoots = getQuadraticRoots,
+ getAngle = getAngle,
+ },
+ circle = {
+ getArea = getCircleArea,
+ checkPoint = checkCirclePoint,
+ isPointOnCircle = isPointOnCircle,
+ getCircumference = getCircumference,
+ getLineIntersection = getCircleLineIntersection,
+ getSegmentIntersection = getCircleSegmentIntersection,
+ getCircleIntersection = getCircleCircleIntersection,
+ isCircleCompletelyInside = isCircleCompletelyInsideCircle,
+ isPolygonCompletelyInside = isPolygonCompletelyInsideCircle,
+ isSegmentCompletelyInside = isSegmentCompletelyInsideCircle,
+
+ -- Aliases
+ getPolygonIntersection = getPolygonCircleIntersection,
+ isCircleInsidePolygon = isCircleInsidePolygon,
+ isCircleCompletelyInsidePolygon = isCircleCompletelyInsidePolygon,
+ },
+ polygon = {
+ getSignedArea = getSignedPolygonArea,
+ getArea = getPolygonArea,
+ getTriangleHeight = getTriangleHeight,
+ getCentroid = getCentroid,
+ getLineIntersection = getPolygonLineIntersection,
+ getSegmentIntersection = getPolygonSegmentIntersection,
+ checkPoint = checkPolygonPoint,
+ isSegmentInside = isSegmentInsidePolygon,
+ getPolygonIntersection = getPolygonPolygonIntersection,
+ getCircleIntersection = getPolygonCircleIntersection,
+ isCircleInside = isCircleInsidePolygon,
+ isPolygonInside = isPolygonInsidePolygon,
+ isCircleCompletelyInside = isCircleCompletelyInsidePolygon,
+ isSegmentCompletelyInside = isSegmentCompletelyInsidePolygon,
+ isPolygonCompletelyInside = isPolygonCompletelyInsidePolygon,
+
+ -- Aliases
+ isCircleCompletelyOver = isPolygonCompletelyInsideCircle,
+ },
+ statistics = {
+ getMean = getMean,
+ getMedian = getMedian,
+ getMode = getMode,
+ getRange = getRange,
+ getVariance = getVariance,
+ getStandardDeviation = getStandardDeviation,
+ getCentralTendency = getCentralTendency,
+ getVariationRatio = getVariationRatio,
+ getDispersion = getDispersion,
+ },
+}