From 42f6fe0a3beca06ed17df7870bfc8f50aae3a509 Mon Sep 17 00:00:00 2001 From: Vodkannelle Date: Sat, 8 May 2021 12:07:20 +0200 Subject: [PATCH] yet again changed physics dep, shit --- deps/physics.lua | 420 +++++++++++ deps/windfield/init.lua | 929 ------------------------- deps/windfield/mlib/Changes.txt | 568 --------------- deps/windfield/mlib/LICENSE.md | 17 - deps/windfield/mlib/README.md | 890 ------------------------ deps/windfield/mlib/mlib.lua | 1152 ------------------------------- 6 files changed, 420 insertions(+), 3556 deletions(-) create mode 100644 deps/physics.lua delete mode 100644 deps/windfield/init.lua delete mode 100644 deps/windfield/mlib/Changes.txt delete mode 100644 deps/windfield/mlib/LICENSE.md delete mode 100644 deps/windfield/mlib/README.md delete mode 100644 deps/windfield/mlib/mlib.lua diff --git a/deps/physics.lua b/deps/physics.lua new file mode 100644 index 0000000..b80eb0b --- /dev/null +++ b/deps/physics.lua @@ -0,0 +1,420 @@ +--[[ + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 1.0, March 2000 + + Copyright (C) 2020 - 4v0v + + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Ok, the purpose of this license is simple + and you just + + DO WHAT THE FUCK YOU WANT TO. +]]-- + +local World, Collider, Shape, lg, lp = {}, {}, {}, love.graphics, love.physics +local _uid = function() local fn = function() local r = math.random(16) return ("0123456789ABCDEF"):sub(r, r) end return ("xxxxxxxxxxxxxxxx"):gsub("[x]", fn) end +local _set_funcs = function(a, ...) + local args = {...} + local _f = {__gc=0,__eq=0,__index=0,__tostring=0,isDestroyed=0,testPoint=0,getType=0,raycast=0,destroy=0,setUserData=0,getUserData=0,release=0,type=0,typeOf=0} + for _, arg in pairs(args) do for k, v in pairs(arg.__index) do if not _f[k] then a[k] = function(a, ...) return v(arg, ...) end end end end +end + +------------------------------- +-- <°)))>< <°)))>< <°)))>< -- +------------------------------- + +function World:new(xg, yg, sleep) + local function _callback(callback, fix1, fix2, contact, ...) + if fix1:getUserData() and fix2:getUserData() then + local shape1, shape2 = fix1:getUserData() , fix2:getUserData() + local coll1 , coll2 = fix1:getBody():getUserData(), fix2:getBody():getUserData() + local ctitle = coll1._id .. "\t" .. coll2._id + local stitle = shape1._id .. "\t" .. shape2._id + local world = coll1._world + + world[callback](shape1, shape2, contact, false, ...) + shape1[callback](shape1, shape2, contact, false, ...) + shape2[callback](shape2, shape1, contact, true, ...) + if callback == "_pre" or callback == "_post" then + coll1[callback](shape1, shape2, contact, false) + coll2[callback](shape2, shape1, contact, true) + + elseif callback == "_enter" then + if not world._collisions[ctitle] then + world._collisions[ctitle] = {} + coll1._enter(shape1, shape2, contact, false) + coll2._enter(shape2, shape1, contact, true) + end + table.insert(world._collisions[ctitle], stitle) + elseif callback == "_exit" then + for i,v in pairs(world._collisions[ctitle]) do if v == stitle then table.remove(world._collisions[ctitle], i) break end end + if #world._collisions[ctitle] == 0 then + world._collisions[ctitle] = nil + coll1._exit(shape1, shape2, contact, false) + coll2._exit(shape2, shape1, contact, true) + end + end + end + end + local function _enter(fix1, fix2, contact) _callback("_enter", fix1, fix2, contact) end + local function _exit(fix1, fix2, contact) _callback("_exit" , fix1, fix2, contact) end + local function _pre(fix1, fix2, contact) _callback("_pre" , fix1, fix2, contact) end + local function _post(fix1, fix2, contact, ...) _callback("_post" , fix1, fix2, contact, ...) end -- ... => normal_impulse1, tangent_impulse1, normal_impulse2, tangent_impulse2 + ----------------------------- + local obj = { + _b2d = lp.newWorld(xg, yg, sleep), + _colliders = {}, + _joints = {}, + _classes = {}, + _classes_mask = {}, + _collisions = {}, + _queries = {}, + _query_color= {0, 0.8, 1, 1}, + _joint_color= {1, 0.5, 0.25, 1}, + _enter = function() end, + _exit = function() end, + _pre = function() end, + _post = function() end + } + ----------------------------- + _set_funcs(obj, obj._b2d) + setmetatable(obj, {__index = World}) + obj:setCallbacks(_enter, _exit, _pre, _post) + obj:addClass("Default") + + return obj +end +function World:draw() + local _r, _g, _b, _a = lg.getColor() + -- Colliders -- + for k1,v1 in pairs(self:getBodies()) do for k2, v2 in pairs(v1:getFixtures()) do + local _shape = v2:getUserData() + lg.setColor(_shape._color.r, _shape._color.g, _shape._color.b, _shape._color.a) + if v2:getShape():getType() == "circle" then + local _x, _y = v2:getShape():getPoint() + lg.push() + lg.translate(v1:getX(), v1:getY()) + lg.rotate(v1:getAngle()) + lg.circle(_shape._draw_mode, _x, _y, v2:getShape():getRadius()) + lg.pop() + elseif v2:getShape():getType() == "polygon" then lg.polygon(_shape._draw_mode, v1:getWorldPoints(v2:getShape():getPoints())) + else local _p = {v1:getWorldPoints(v2:getShape():getPoints())}; for i=1, #_p, 2 do if i < #_p-2 then lg.line(_p[i], _p[i+1], _p[i+2], _p[i+3]) end end end + end end + -- Joints -- + lg.setColor(self._joint_color) + for _, joint in ipairs(self:getJoints()) do + local x1, y1, x2, y2 = joint:getAnchors() + if x1 and y1 then lg.circle('line', x1, y1, 6) end + if x2 and y2 then lg.circle('line', x2, y2, 6) end + end + -- Queries -- + lg.setColor(self._query_color) + for i = #self._queries, 1, -1 do + local _query = self._queries[i] + if _query.type == "circle" then lg.circle("line", _query.x, _query.y, _query.r) + elseif _query.type =="rectangle" then lg.rectangle("line", _query.x, _query.y, _query.w, _query.h) + elseif _query.type == "polygon" then lg.polygon("line", _query.vertices) + elseif _query.type == "line" then lg.line(_query.x1, _query.y1, _query.x2, _query.y2) end + _query.frames = _query.frames - 1 + if _query.frames == 0 then table.remove(self._queries, i) end + end + lg.setColor(_r, _g, _b, _a) +end +function World:setQueryColor(r,g,b,a) self._query_color = {r, g, b, a} return self end +function World:setJointColor(r,g,b,a) self._joint_color = {r, g, b, a} return self end +function World:setEnter(fn) self._enter = fn return self end +function World:setExit(fn) self._exit = fn return self end +function World:setPresolve(fn) self._pre = fn return self end +function World:setPostsolve(fn) self._post = fn return self end +function World:addClass(tag, ignore) + local function sa(t1, t2) for k in pairs(t1) do if not t2[k] then return false end end for k in pairs(t2) do if not t1[k] then return false end end return true end + local function a(g) local r = {} for l, _ in pairs(g) do r[l] = {} for k,v in pairs(g) do for _ ,v2 in pairs(v) do if v2 == l then r[l][k] = "" end end end end return r end + local function b(g) local r = {} for k,v in pairs(g) do table.insert(r, v) end for i = #r, 1,-1 do local s = false for j = #r, 1, -1 do if i ~= j and sa(r[i], r[j]) then s = true end end if s then table.remove( r, i ) end end return r end + local function c(t1, t2) local r = {} for i, v in pairs(t2) do for l,v2 in pairs(t1) do if sa(v, v2) then r[l] = i end end end return r end + ----------------------------- + local ignore = ignore or {} + self._classes[tag] = ignore + self._classes_mask = c(a(self._classes), b(a(self._classes))) + + for k,v in pairs(self._colliders) do v:setClass(v._class) end + return self +end +function World:addJoint(joint_type, col1, col2, ...) + local _jt,_joint, _j = joint_type, {} + if _jt == "distance" then _j = lp.newDistanceJoint(col1._body, col2._body, ...) + elseif _jt == "friction" then _j = lp.newFrictionJoint(col1._body, col2._body, ...) + elseif _jt == "gear" then _j = lp.newGearJoint(col1._joint, col2._joint, ...) + elseif _jt == "motor" then _j = lp.newMotorJoint(col1._body, col2._body, ...) + elseif _jt == "mouse" then _j = lp.newMouseJoint(col1._body, col2, ...) -- col2 = x, ... = y + elseif _jt == "prismatic" then _j = lp.newPrismaticJoint(col1._body, col2._body, ...) + elseif _jt == "pulley" then _j = lp.newPulleyJoint(col1._body, col2._body, ...) + elseif _jt == "revolute" then _j = lp.newRevoluteJoint(col1._body, col2._body, ...) + elseif _jt == "rope" then _j = lp.newRopeJoint(col1._body, col2._body, ...) + elseif _jt == "weld" then _j = lp.newWeldJoint(col1._body, col2._body, ...) + elseif _jt == "wheel" then _j = lp.newWheelJoint(col1._body, col2._body, ...) end + ----------------------------- + _joint._id = _uid() + _joint._joint = _j + ----------------------------- + _set_funcs(_joint, _joint._joint) + self._joints[_joint._id] = _joint + + return _joint +end +function World:addCollider(collider_type, ...) + local _w, _ct, _a, _collider, _b, _s = self._b2d, collider_type, {...}, {} + if _ct == "circle" then _b, _s = lp.newBody(_w, _a[1], _a[2], _a[4] or "dynamic"), lp.newCircleShape(_a[3]) + elseif _ct == "rectangle" then _b, _s = lp.newBody(_w, _a[1], _a[2], _a[6] or "dynamic"), lp.newRectangleShape(0, 0, _a[3], _a[4], _a[5] or 0) + elseif _ct == "polygon" then _b, _s = lp.newBody(_w, _a[1], _a[2], _a[4] or "dynamic"), lp.newPolygonShape(unpack(_a[3])) + elseif _ct == "line" then _b, _s = lp.newBody(_w, 0, 0, _a[5] or "static" ), lp.newEdgeShape(_a[1], _a[2], _a[3], _a[4]) + elseif _ct == "chain" then _b, _s = lp.newBody(_w, 0, 0, _a[3] or "static" ), lp.newChainShape(_a[1], unpack(_a[2])) end + ----------------------------- + _collider._world = self + _collider._id = _uid() + _collider._tag = _collider._id + _collider._class = "" + _collider._enter = function() end + _collider._exit = function() end + _collider._pre = function() end + _collider._post = function() end + _collider._body = _b + _collider._shapes = { + main = { + _collider = _collider, + _id = "main_" .. _collider._id, + _tag = "main", + _shape = _s, + _fixture = lp.newFixture(_b, _s, 1), + _enter = function() end, + _exit = function() end, + _pre = function() end, + _post = function() end, + _is_visible = true, + _color = {r=1, g=1, b=1, a=1}, + _draw_mode = "line" + } + } + _collider._data = {} + _collider._is_visible = true + _collider._color = {r=1, g=1, b=1, a=1} + _collider._draw_mode = "line" + ----------------------------- + _collider._shapes["main"]._fixture:setUserData(_collider._shapes["main"]) + _collider._body:setUserData(_collider) + _set_funcs(_collider, _collider._body, _collider._shapes["main"]._shape, _collider._shapes["main"]._fixture) + setmetatable(_collider._shapes["main"], {__index = Shape}) + setmetatable(_collider, {__index = Collider}) + _collider:setClass("Default") + self._colliders[_collider._id] = _collider + + return _collider +end +function World:addCircle(x, y, r, type) return self:addCollider("circle" , x, y, r, type) end +function World:addRectangle(x, y, w, h, rad, type) return self:addCollider("rectangle", x, y, w, h, rad, type) end +function World:addPolygon(x, y, vertices, type) return self:addCollider("polygon" , x, y, vertices, type) end +function World:addLine(x1, y1, x2, y2, type) return self:addCollider("line" , x1, y1, x2, y2, type) end +function World:addChain(loop, vertices, type) return self:addCollider("chain" , loop, vertices, type) end +function World:queryCircle(x, y, r, class) + local _colliders_list = {} + for k,v in pairs(self._colliders) do + local _x,_y = v:getPosition() + if math.sqrt((_x - x)^2 + (_y - y)^2) <= r then + if not class then table.insert(_colliders_list, v) + elseif class then if v:getClass() == class then table.insert(_colliders_list, v) end end + end + end + table.insert(self._queries, {type = "circle", x = x, y = y, r = r, frames = 80 }) + return _colliders_list +end +function World:queryRectangle(x, y, w, h, class) + local _colliders_list = {} + for k,v in pairs(self._colliders) do + local _x,_y = v:getPosition() + if _x >= x and _x <= x + w and _y >= y and _y <= y + h then + if not class then table.insert(_colliders_list, v) + elseif class then if v:getClass() == class then table.insert(_colliders_list, v) end end + end + end + table.insert(self._queries, {type = "rectangle", x = x, y = y, w = w, h = h, frames = 80 }) + return _colliders_list +end +function World:queryPolygon(vertices, class) + local _colliders_list = {} + for k,v in pairs(self._colliders) do + local _x,_y = v:getPosition() + local _collision, _next = false, 1 + for i = 1, #vertices, 2 do + _next = i + 2 + if _next > #vertices then _next = 1 end + local _vcx, _vcy, _vnx, _vny = vertices[i], vertices[i+1], vertices[_next], vertices[_next+1] + if (((_vcy >= _y and _vny < _y) or (_vcy < _y and _vny >= _y)) + and (_x < (_vnx-_vcx)*(_y-_vcy)/ (_vny-_vcy) + _vcx)) then + _collision = not _collision + end + end + if _collision then + if not class then table.insert(_colliders_list, v) + elseif class then if v:getClass() == class then table.insert(_colliders_list, v) end end + end + end + table.insert(self._queries, {type = "polygon", vertices = vertices, frames = 80 }) + return _colliders_list +end +function World:queryLine(x1, y1, x2, y2, class) + local _colliders_list, _colliders_tag = {}, {} + self._b2d:rayCast(x1, y1, x2, y2, function(fixture) + if not class then + if not _colliders_tag[fixture:getUserData():getCtag()] then + table.insert(_colliders_list, fixture:getUserData():getCollider()) + _colliders_tag[fixture:getUserData():getCtag()] = "flatisjustice" + end + else + if fixture:getUserData():getCollider():getClass() == class then + if not _colliders_tag[fixture:getUserData():getCtag()] then + table.insert(_colliders_list, fixture:getUserData():getCollider()) + _colliders_tag[fixture:getUserData():getCtag()] = "flatisjustice" + end + end + end + return 1 + end) + table.insert(self._queries, {type = "line", x1 = x1, y1 = y1, x2 = x2, y2 = y2, frames = 80 }) + return _colliders_list +end +function World:destroy() + for k,v in pairs(self._colliders) do v:destroy() end + for k,v in pairs(self._joints) do v:destroy() end + self.box2d:destroy() + for k,v in pairs(self) do v = nil end +end + +------------------------------- +-- <°)))>< <°)))>< <°)))>< -- +------------------------------- + +function Collider:setClass(class) + local class = class or "Default" + assert( self._world._classes[class] , "Class " .. class .. " is undefined.") + self._class = class + local tmask = {} + for _, v in pairs(self._world._classes[class]) do table.insert(tmask, self._world._classes_mask[v]) end + for k, v in pairs(self._shapes) do v._fixture:setCategory(self._world._classes_mask[class]) v._fixture:setMask(unpack(tmask))end + return self +end +function Collider:setEnter(fn) self._enter = fn return self end +function Collider:setExit(fn) self._exit = fn return self end +function Collider:setPresolve(fn) self._pre = fn return self end +function Collider:setPostsolve(fn) self._post = fn return self end +function Collider:setData(data) self._data = data return self end +function Collider:setTag(tag) self._tag = tag return self end +function Collider:getClass() return self._class end +function Collider:getTag() return self._tag end +function Collider:getData(data) return self._data end +function Collider:getPShape(tag) return self._shapes[tag] end +function Collider:addShape(tag, shape_type, ...) + assert(not self._shapes[tag], "Collider already have a shape called '" .. tag .."'.") + local _st, _a, _shape = shape_type, {...} + if _st == "circle" then _shape = lp.newCircleShape(_a[1], _a[2], _a[3]) + elseif _st == "rectangle" then _shape = lp.newRectangleShape(_a[1], _a[2], _a[3], _a[4], _a[5]) + elseif _st == "polygon" then _shape = lp.newPolygonShape(unpack(_a[1])) + elseif _st == "line" then _shape = lp.newEdgeShape(_a[1], _a[2], _a[3], _a[4]) + elseif _st == "chain" then _shape = lp.newChainShape(_a[1], unpack(_a[2])) end + ----------------------------- + self._shapes[tag] = { + _tag = tag, + _id = tag .. "_" .. self._id, + _collider = self, + _shape = _shape, + _fixture = lp.newFixture(self._body, _shape, 1), + _enter = function() end, + _exit = function() end, + _pre = function() end, + _post = function() end, + _is_visible = self._is_visible, + _color = {r = self._color.r, g = self._color.g, b =self._color.b, a =self._color.a}, + _draw_mode = self._draw_mode + } + ----------------------------- + _set_funcs(self._shapes[tag], self._body, self._shapes[tag]._fixture, self._shapes[tag]._shape) + self._shapes[tag]._fixture:setUserData(self._shapes[tag]) + local tmask = {} for _, v in pairs(self._world._classes[self._class]) do table.insert(tmask, self._world._classes_mask[v]) end + self._shapes[tag]._fixture:setCategory(self._world._classes_mask[self._class]) + self._shapes[tag]._fixture:setMask(unpack(tmask)) + return setmetatable(self._shapes[tag], {__index = Shape}) +end +function Collider:setAlpha(a) + self._color.a = a + for _,v in pairs(self._shapes) do v._color.a = a end + return self +end +function Collider:setColor(r, g, b, a) + self._color = {r = r, g = g, b = b, a = a or self._color.a} + for _,v in pairs(self._shapes) do v._color = {r = r, g = g, b = b, a = a or v._color.a} end + return self +end +function Collider:setDrawMode(mode) + self._draw_mode = mode + for _,v in pairs(self._shapes) do v._draw_mode = mode end + return self +end +function Collider:removeShape(tag) + assert(self._shapes[tag], "Shape '" .. tag .. "' doesn't exist.") + for k, v in pairs(self._world._collisions) do + if k:find(self._id) then + for i = #v, 1, -1 do + if v[i]:find(self._shapes[tag]._id) then table.remove(self._world._collisions[k], i) end + end + end + if #v == 0 then self._world._collisions[k] = nil end + end + + self._shapes[tag]._fixture:setUserData(nil) + self._shapes[tag]._fixture:destroy() + self._shapes[tag]._fixture = nil + self._shapes[tag]._collider = nil + self._shapes[tag]._shape = nil + self._shapes[tag] = nil + return self +end +function Collider:destroy() + for k, v in pairs(self._world._collisions) do if k:find(self._id) then self._world._collisions[k] = nil end end + self._world._colliders[self._id] = nil + self._world = nil + + for k,v in pairs(self._shapes) do + v._fixture:setUserData(nil) + v._fixture:destroy() + v._fixture = nil + v._shape = nil + v._collider = nil + end + self._data = nil + self._body:setUserData(nil) + self._body:destroy() + self._body = nil +end + +------------------------------- +-- <°)))>< <°)))>< <°)))>< -- +------------------------------- + +function Shape:setEnter(fn) self._enter = fn return self end +function Shape:setExit(fn) self._exit = fn return self end +function Shape:setPresolve(fn) self._pre = fn return self end +function Shape:setPostsolve(fn) self._post = fn return self end +function Shape:setAlpha(a) self._color.a = a return self end +function Shape:setColor(r, g, b, a) self._color = {r = r, g = g, b = b, a = a or self._color.a} return self end +function Shape:setDrawMode(mode) self._draw_mode = mode return self end +function Shape:getCollider() return self._collider end +function Shape:getClass() return self._collider._class end +function Shape:getCTag() return self._collider._tag end +function Shape:getTag() return self._tag end +function Shape:destroy() self._collider:removeShape(self._tag) end + +------------------------------- +-- <°)))>< <°)))>< <°)))>< -- +------------------------------- + +return setmetatable({}, {__call = World.new}) diff --git a/deps/windfield/init.lua b/deps/windfield/init.lua deleted file mode 100644 index 8554822..0000000 --- a/deps/windfield/init.lua +++ /dev/null @@ -1,929 +0,0 @@ ---[[ -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/deps/windfield/mlib/Changes.txt b/deps/windfield/mlib/Changes.txt deleted file mode 100644 index 1b41d50..0000000 --- a/deps/windfield/mlib/Changes.txt +++ /dev/null @@ -1,568 +0,0 @@ -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/deps/windfield/mlib/LICENSE.md b/deps/windfield/mlib/LICENSE.md deleted file mode 100644 index 0e7071e..0000000 --- a/deps/windfield/mlib/LICENSE.md +++ /dev/null @@ -1,17 +0,0 @@ -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/deps/windfield/mlib/README.md b/deps/windfield/mlib/README.md deleted file mode 100644 index a5efed3..0000000 --- a/deps/windfield/mlib/README.md +++ /dev/null @@ -1,890 +0,0 @@ -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/deps/windfield/mlib/mlib.lua b/deps/windfield/mlib/mlib.lua deleted file mode 100644 index 76067c6..0000000 --- a/deps/windfield/mlib/mlib.lua +++ /dev/null @@ -1,1152 +0,0 @@ ---[[ 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, - }, -}