You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1317 lines
38 KiB
1317 lines
38 KiB
--[[ 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 - 0.00001 <= number2 and number2 <= number1 + 0.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 + 0.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 (
|
|
0.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 + 0.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 + 0.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 + 0.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,
|
|
},
|
|
}
|
|
|