Skip to content

Instantly share code, notes, and snippets.

@HoraceBury
Last active February 27, 2022 12:17
Show Gist options
  • Save HoraceBury/9431861 to your computer and use it in GitHub Desktop.
Save HoraceBury/9431861 to your computer and use it in GitHub Desktop.
Mathematics functions, including trigonometry. The main file demonstrates the use of the various maths extension functions available in the library. To use this remove the '_' from the filename.
-- mathlib.lua
local function dump(tbl)
print("====================")
for k,v in pairs(tbl) do
print(k,v)
end
print("====================")
end
--[[
Maths extension library for use in Corona SDK by Matthew Webster.
All work derived from referenced sources.
Many of these functions are useful for trigonometry and geometry because they have developed for use within graphical user interfaces.
Much of these are useful when building physics games
twitter: @horacebury
blog: http://springboardpillow.blogspot.co.uk/2012/04/sample-code.html
code exchange: http://code.coronalabs.com/search/node/HoraceBury
github: https://gist.github.com/HoraceBury
]]--
--[[
References:
http://stackoverflow.com/questions/1073336/circle-line-segment-collision-detection-algorithm
http://mathworld.wolfram.com/Circle-LineIntersection.html
http://stackoverflow.com/questions/385305/efficient-maths-algorithm-to-calculate-intersections
http://stackoverflow.com/questions/4543506/algorithm-for-intersection-of-2-lines
http://community.topcoder.com/tc?module=Static&d1=tutorials&d2=geometry2#reflection
http://gmc.yoyogames.com/index.php?showtopic=433577
http://local.wasp.uwa.edu.au/~pbourke/geometry/
http://alienryderflex.com/polygon/
http://alienryderflex.com/polygon_fill/
http://www.amazon.com/dp/1558607323/?tag=stackoverfl08-20
http://www.amazon.co.uk/s/ref=nb_sb_noss_1?url=search-alias%3Daps&field-keywords=Real-Time+Collision+Detection
http://en.wikipedia.org/wiki/Line-line_intersection
http://developer.coronalabs.com/forum/2010/11/17/math-helper-functions-distancebetween-and-anglebetween
http://www.mathsisfun.com/algebra/vectors-dot-product.html
http://www.mathsisfun.com/algebra/vector-calculator.html
http://lua-users.org/wiki/PointAndComplex
http://www.math.ntnu.no/~stacey/documents/Codea/Library/Vec3.lua
http://www.iforce2d.net/forums/viewtopic.php?f=4&t=79&sid=b9ecd62533361594e321de04b3929d4f
http://rosettacode.org/wiki/Dot_product#Lua
http://chipmunk-physics.net/forum/viewtopic.php?f=1&t=2215
http://www.fundza.com/vectors/normalize/index.html
http://www.mathopenref.com/coordpolygonarea2.html
http://stackoverflow.com/questions/2705542/returning-the-nearest-multiple-value-of-a-number
http://members.tripod.com/c_carleton/dotprod.html/
http://www.1728.org/density.htm
http://www.wikihow.com/Find-the-Angle-Between-Two-Vectors
http://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect
http://www.euclideanspace.com/maths/algebra/vectors/angleBetween/
http://stackoverflow.com/questions/9079853/lua-print-integer-as-a-binary
http://www.mathopenref.com/coordintersection.html
http://www.wikihow.com/Algebraically-Find-the-Intersection-of-Two-Lines
]]--
--[[
Deprecated functions (see revisions for code):
rad = convertDegreesToRadians( degrees )
deg = convertRadiansToDegrees( radians )
polygonFill( points, closed, perPixel, width, height, col )
]]--
--[[
Multiplication & Fractions Functions:
]]--
--[[
Point Functions:
]]--
--[[
Angle Functions:
]]--
--[[
Line Functions:
]]--
--[[
Polygon Functions:
]]--
--[[
Point Functions:
]]--
--[[
Fractions
]]--
-- returns the average of all parameters passed in
function math.avg( ... )
return math.sum( unpack( arg ) ) / #arg
end
-- returns the sum total of all parameters passed in
function math.sum( ... )
local total = 0
for i=1, #arg do
total = total + arg[i]
end
return total
end
-- Rounds to the nearest multiple of the number.
function math.nearest( number, multiple )
return math.round( number / multiple ) * multiple
end
-- Return the column or row index
function math.getColIndex( x, colSize )
return math.floor( x / colSize ) + 1
end
--[[
Rounds all numbers up to the next nearest multiple of the first parameter.
]]--
local function roundUpTo( upto, ... )
local t = {}
for i=1, #arg do
t[i] = arg[i] + (upto - arg[i] % upto)
end
return unpack(t)
end
math.roundUpTo = roundUpTo
local function test_Rounding()
for i=0, 20 do
print(i,math.nearest(i,4),math.roundUpTo(4,i))
end
end
--test_Rounding()
-- Returns b represented as a fraction of a.
-- Eg: If a is 1000 and b is 900 the returned value is 0.9
-- Often the returned value would be used in a multiplication of another value, usually a distance value.
local function fractionOf( a, b )
return b / a
end
math.fractionOf = fractionOf
-- Returns b represented as a percentage of a.
-- Eg: If a is 1000 and b is 900 the returned value is 90
-- Use: This is useful in determining how far something should be moved to complete a certain distance.
-- Often the returned value would be used in a division of another value, usually a distance value.
local function percentageOf( a, b )
return fractionOf(a, b) * 100
end
math.percentageOf = percentageOf
-- return a value clamped between a range
local function clamp( val, low, high )
if (val < low) then return low end
if (val > high) then return high end
return val
end
math.clamp = clamp
--[[
Returns the time to use in a transition when setting a transition.to
Parameters:
totalTime: Time spent transitioning from 0 to the full value
fullValue: The target value to transition towards
currentValue: The partial value. 0 if no transition has occurred yet, otherwise the incomplete intermediate value
Returns:
The value used to set the 'time' property of a transition.to call.
]]--
local function transitionTime( totalTime, fullValue, currentValue )
return totalTime - (totalTime /( fullValue/currentValue ))
end
math.transitionTime = transitionTime
--[[
Angles
]]--
-- rotates point around the centre by degrees
-- rounds the returned coordinates using math.round() if round == true
-- returns new coordinates object
local function rotateAboutPoint( point, degrees, centre )
local pt = { x=point.x - centre.x, y=point.y - centre.y }
pt = math.rotateTo( pt, degrees )
pt.x, pt.y = pt.x + centre.x, pt.y + centre.y
return pt
end
math.rotateAboutPoint = rotateAboutPoint
-- rotates a point around the (0,0) point by degrees
-- returns new point object
-- center: optional
local function rotateTo( point, degrees, center )
if (center ~= nil) then
return rotateAboutPoint( point, degrees, center )
else
local x, y = point.x, point.y
local theta = math.rad( degrees )
local pt = {
x = x * math.cos(theta) - y * math.sin(theta),
y = x * math.sin(theta) + y * math.cos(theta)
}
return pt
end
end
math.rotateTo = rotateTo
local function rotate( x, y, angleInDegrees )
local angleInRadians = math.rad( angleInDegrees )
local cosRadians, sinRadians = math.cos( angleInRadians ), math.sin( angleInRadians )
return x * cosRadians - y * sinRadians, x * sinRadians + y * cosRadians
end
math.rotate = rotate
--[[
Rotates a point clockwise or anticlockwise 90 degrees only.
Fast implementation.
Parameters option 1:
xCenter: X of point to rotate around - optional
yCenter: Y of point to rotate around - optional
xPoint: X of point to rotate
yPoint: Y of point to rotate
angle: Amount to rotate -90 or false, 90 or true - optional: default is 90
Parameters option 2:
{x,y}: Point to rotate around - optional
{x,y}: Point to rotate
angle: Amount to rotate -90 or false, 90 or true - optional: default is 90
Returns - Parameters option 1:
xPoint: X of rotated point
yPoint: Y of rotated point
Returns - Parameters option 2:
{x,y}: Rotated point
]]--
local function rotateBy90( ... )
--print(arg[1],arg[2])
local xCenter, yCenter = 0, 0
local xPt, yPt = 0, 0
local angle = 90
if (type(arg[1]) == "table") then
if (#arg == 1) then
xPt, yPt = arg[1].x, arg[1].y
elseif (#arg == 2 and type(arg[2]) == "table") then
xCenter, yCenter = arg[1].x, arg[1].y
xPt, yPt = arg[2].x, arg[2].y
elseif (#arg == 2 and type(arg[2]) == "number") then
xPt, yPt = arg[1].x, arg[1].y
angle = arg[2]
else
xCenter, yCenter = arg[1].x, arg[1].y
xPt, yPt = arg[2].x, arg[2].y
angle = arg[3]
end
else
if (#arg == 2) then
xPt, yPt = unpack( arg )
elseif (#arg == 3) then
xPt, yPt, angle = unpack( arg )
elseif (#arg == 4) then
xCenter, yCenter, xPt, yPt = unpack( arg )
else
xCenter, yCenter, xPt, yPt, angle = unpack( arg )
end
end
if (type(angle) == "boolean") then
if (angle) then
angle = 90
else
angle = -90
end
end
local dx, dy = xPt-xCenter, yPt-yCenter
if (angle == -90) then
xPt, yPt = dy, -dx
else
xPt, yPt = -dy, dx
end
if (type(arg[1]) == "table") then
return {x=xCenter+xPt,y=yCenter+yPt}
else
return xCenter+xPt, yCenter+yPt
end
end
math.rotateBy90 = rotateBy90
local function test_rotateBy90()
local center = display.newCircle( display.actualCenterX, display.actualCenterY, 25 )
local rotated = display.newCircle( display.actualCenterX, display.actualCenterY-250, 25 )
local rotatedforward = display.newCircle( display.actualCenterX, display.actualCenterY-250, 25 )
local dot = display.newCircle( display.actualCenterX, display.actualCenterY-250, 25 )
dot.fill = {0,0,1}
rotated.fill = {1,0,0}
rotatedforward.fill = {0,1,0}
dot:addEventListener( "touch", function(e)
if (e.phase == "began") then
display.currentStage:setFocus( e.target )
elseif (e.phase == "ended") then
display.currentStage:setFocus( nil )
return false
end
dot.x, dot.y = e.x, e.y
local ptB = math.rotateBy90( center, dot, true )
local ptF = math.rotateBy90( center, dot, -90 )
rotated.x, rotated.y = ptB.x, ptB.y
rotatedforward.x, rotatedforward.y = ptF.x, ptF.y
return true
end )
end
--test_rotateBy90()
--[[ Support values for angles ]]--
local PI = (4*math.atan(1))
local quickPI = 180 / PI
math.PI, math.quickPI = PI, quickPI
--[[
Converts an integer to a string of bits, least significant first.
Ref: http://stackoverflow.com/questions/9079853/lua-print-integer-as-a-binary
]]--
function math.toBits(num)
local t={} -- will contain the bits
while num>0 do
rest=math.fmod(num,2)
t[#t+1]=rest
num=(num-rest)/2
end
return table.concat(t)
end
--[[
Returns the angle.
Params:
a : Returns the angle of the point at a relative to (0,0) (east is the virtual base)
a, b Params: Returns the angle of b relative to a
a, b, c Params: Returns the angle found at a for between b and c
]]--
local function angleOf( ... )
local a, b, c, d = arg[1], arg[2], arg[3], arg[4]
if (#arg == 1 and arg[1].xStart == nil) then
-- angle of a relative to (0,0)
return math.atan2( a.y, a.x ) * quickPI -- 180 / PI -- math.pi
elseif (#arg == 1 and arg[1].xStart ~= nil) then
-- angle of x,y relative to xStart,yStart
return math.atan2( a.y - a.yStart, a.x - a.xStart ) * quickPI -- 180 / PI -- math.pi
elseif (#arg == 2 and type(arg[1]) == "number") then
-- angle of b relative to a
return math.atan2( b, a ) * quickPI -- 180 / PI -- math.pi
elseif (#arg == 2) then
-- angle of b relative to a
return math.atan2( b.y - a.y, b.x - a.x ) * quickPI -- 180 / PI -- math.pi
elseif (#arg == 3) then
-- angle between b and c found at a
local deg = angleOf( a, b ) - angleOf( a, c ) -- target - source
if (deg > 180) then
deg = deg - 360
elseif (deg < -180) then
deg = deg + 360
end
return deg
elseif (#arg == 4) then
-- angle of x,y,x,y at second x,y relative to first x,y
return math.atan2( d - b, c - a ) * quickPI -- 180 / PI -- math.pi
end
-- wrong set of parameters
return nil
end
math.angleOf = angleOf
-- Brent Sorrentino
-- Returns the angle between the objects
local function angleBetween( srcObj, dstObj )
local xDist = dstObj.x - srcObj.x
local yDist = dstObj.y - srcObj.y
local angleBetween = math.deg( math.atan( yDist / xDist ) )
if ( srcObj.x < dstObj.x ) then
angleBetween = angleBetween + 90
else
angleBetween = angleBetween - 90
end
return angleBetween
end
math.angleBetween = angleBetween
--[[
Calculate the angle between two lines.
Params:
lineA - The first line { a={x,y}, b={x,y} }
lineA - The first line { a={x,y}, b={x,y} }
]]--
local function angleBetweenLines( lineA, lineB )
local angle1 = math.atan2( lineA.a.y - lineA.b.y, lineA.a.x - lineA.b.x )
local angle2 = math.atan2( lineB.a.y - lineB.b.y, lineB.a.x - lineB.b.x )
return math.deg( angle1 - angle2 )
end
math.angleBetweenLines = angleBetweenLines
-- returns the smallest angle between the two angles
-- ie: the difference between the two angles via the shortest distance
-- returned value is signed: clockwise is negative, anticlockwise is positve
-- returned value wraps at +/-180
-- Example code to rotate a display object by touch:
--[[
-- called in the "moved" phase of touch event handler
local a = mathlib.angleBetween( target, target.prevevent )
local b = mathlib.angleBetween( target, event )
local d = mathlib.smallestAngleDiff( a, b )
target.prev = event
target.rotation = target.rotation - d
]]--
local function smallestAngleDiff( target, source )
local a = target - source
if (a > 180) then
a = a - 360
elseif (a < -180) then
a = a + 360
end
return a
end
math.smallestAngleDiff = smallestAngleDiff
-- Returns the angle in degrees between the first and second points, measured at the centre
-- Always a positive value
local function angleAt( centre, first, second )
local a, b, c = centre, first, second
local ab = math.lengthOf( a, b )
local bc = math.lengthOf( b, c )
local ac = math.lengthOf( a, c )
local angle = math.deg( math.acos( (ab*ab + ac*ac - bc*bc) / (2 * ab * ac) ) )
return angle
end
math.angleAt = angleAt
-- Returns true if the point is within the angle at centre measured between first and second
local function isPointInAngle( centre, first, second, point )
local range = math.angleAt( centre, first, second )
local a = math.angleAt( centre, first, point )
local b = math.angleAt( centre, second, point )
-- print(range,a+b)
return math.round(range) >= math.round(a + b)
end
math.isPointInAngle = isPointInAngle
-- Forces to apply based on total force and desired angle
-- http://developer.anscamobile.com/code/virtual-dpadjoystick-template
local function forcesByAngle(totalForce, angle)
local forces = {}
local radians = -math.rad(angle)
forces.x = math.cos(radians) * totalForce
forces.y = math.sin(radians) * totalForce
return forces
end
math.forcesByAngle = forcesByAngle
--[[
Lines and Vectors
]]--
--[[
Returns the length of a line.
Takes either:
x,y - two parameters with the end location of a line as ( x, y ) and (0,0) as the start
{x,y} - one parameter with the end location of a line as {x,y} and {x=0,y=0} as the start
{x,y},{x,y} - two parameters as the start and end of the line as {x,y} and {x,y}
x,y,x,y - four parameters as the start and end of the line as ( x, y, x, y )
Returns:
The length of the line.
]]--
function math.lengthOf( ... )
local a, b
if (#arg == 4 and type(arg[1]) == "number") then
-- four parameters spelling out {x, y, x, y}
a = { x=arg[1], y=arg[2] }
b = { x=arg[3], y=arg[4] }
elseif (#arg == 2 and arg[1].x ~= nil and arg[2].y ~= nil) then
-- two parameters as {.x,.y} for each end
a = arg[1]
b = arg[2]
elseif (#arg == 1 and arg[1].xStart ~= nil) then
-- touch event as the start and end
a = {x=arg[1].xStart, y=arg[1].yStart}
b = arg[1]
elseif (#arg > 4 and type(arg[1]) == "number" and #arg % 2 == 0) then
-- list of numbers as a series of x,y pairs {x,y,...}
local len = 0
for i=1, #arg-3, 2 do
len = len + math.lengthOf( arg[i], arg[i+1], arg[i+2], arg[i+3] )
end
return len
elseif (#arg == 1 and arg[1].a ~= nil and arg[1].b ~= nil) then
-- one parameter containing a and b as the line
a = arg[1].a
b = arg[1].b
elseif (#arg == 2 and type(arg[1]) == "number") then
-- two parameters spelling out x and y of one end
a = { x=arg[1], y=arg[2] }
b = { x=0, y=0 }
elseif (#arg == 1 and arg[1].x ~= nil) then
-- one parameter as the x,y end
a = arg[1]
b = { x=0, y=0 }
end
local width, height = b.x-a.x, b.y-a.y
return (width*width + height*height)^0.5 -- math.sqrt(width*width + height*height)
-- nothing wrong with math.sqrt, but I believe the ^.5 is faster
end
local function testLengthOf()
print( 10 == math.lengthOf( 0,0 , 10,0 ) )
print( 10 == math.lengthOf( 0,10 , 10,10 ) )
print( 20 == math.lengthOf( 0,0 , 10,0 , 10,10 ) )
end
--testLengthOf()
--[[
Description:
Extends the point away from or towards the origin to the length of len.
Params:
origin = Point to extrude away from, pass nil to default to {x=0,y=0}
max =
If param max is nil then the lenOrMin value is the distance to calculate the point's location
If param max is not nil then the lenOrMin value is the minimum clamping distance to extrude to
lenOrMin = the length or the minimum length to extrude the point's distance to
max = the maximum length to extrude to
aspoint = true to return as a table containing {x,y}, default: false
Returns:
x, y = extruded point in real space (this is probably the one you want)
x, y = extruded point relative to origin point
]]--
local function extrudeToLen( origin, point, lenOrMin, max, aspoint )
origin = origin or {x=0,y=0}
local length = math.lengthOf( origin, point )
if (length == 0) then
return origin.x, origin.y
end
local len = lenOrMin
if (max ~= nil) then
if (length < lenOrMin) then
len = lenOrMin
elseif (length > max) then
len = max
else -- the point is within the min/max clamping range
return point.x, point.y
end
end
local factor = len / length
local x, y = (point.x - origin.x) * factor, (point.y - origin.y) * factor
if (aspoint) then
return { x=x + origin.x, y=y + origin.y }, { x=x, y=y }
else
return x + origin.x, y + origin.y, x, y
end
end
math.extrudeToLen = extrudeToLen
-- returns true when the point is on the right of the line formed by the north/south points
local function isOnRight( north, south, point )
local a, b, c = north, south, point
local factor = (b.x - a.x)*(c.y - a.y) - (b.y - a.y)*(c.x - a.x)
return factor > 0, factor
end
math.isOnRight = isOnRight
--[[
Reflect point across line.
Parameters:
line: Line with ends {a,b} each end in the form {x,y}
point: {x,y} point to be reflected
Parameters:
a, b: Line ends of form {x,y}
point: Point to reflect across the a-b line, also form of {x,y}
Returns:
{x,y} point on other side of the line, as if reflected in a mirror.
]]--
local function reflectPointAcrossLine( ... )
local north, south, point
if (#arg == 2) then -- line and point
north = arg[1].a
south = arg[1].b
point = arg[2]
elseif (#arg == 3) then -- line end a, line end b, point
north = arg[1]
south = arg[2]
point = arg[3]
elseif (#arg == 6) then -- line a x, line a y, line b x, line b y, pt x, pt y
north = { x=arg[1], y=arg[2] }
south = { x=arg[3], y=arg[4] }
point = { x=arg[5], y=arg[6] }
else
return nil
end
local x1, y1, x2, y2 = north.x, north.y, south.x, south.y
local x3, y3 = point.x, point.y
local x4, y4 = 0, 0 -- reflected point
local dx, dy, t, d
dx = y2 - y1
dy = x1 - x2
t = dx * (x3 - x1) + dy * (y3 - y1)
t = t / (dx * dx + dy * dy)
x = x3 - 2 * dx * t
y = y3 - 2 * dy * t
return { x=x, y=y }
end
math.reflectPointAcrossLine = reflectPointAcrossLine
--[[
Bounces a point against a line. The point must have a velocity property of form {x,y}
If the line between pt and pt+pt.velocity does not intersect with the line parameter only nil is returned
Three points are returned. First, the point where pt would be if it's velocity property were applied is reflected across the line.
Second, using a line at a right angle drawn out from the intersection point the pt location is reflected.
Third, the point of the bounce on the line.
Parameters:
line: The line to bounce the point against, of form { a={x,y}, b={x,y} }
pt: The point to bounce against the line, of form {x,y}
Returns:
Success = true if the point bounced off the line, otherwise false
Bounced point using the line (includes new .velocity)
Reflected pt using the line at the intersection's right angle
Point of intersection where the pt will cross the line
]]--
local function bouncePointAgainstLine( line, pt )
local nextPt = { x=pt.x+pt.velocity.x, y=pt.y+pt.velocity.y }
local lineB = { a=pt, b=nextPt }
local success, inter = math.doLinesIntersect( line.a, line.b, lineB.a, lineB.b )
if (not success) then
return success, nil -- the velocity of pt does not cause it to make contact with the line
end
local len = math.lengthOf( pt, inter )
local bounce = math.reflectPointAcrossLine( line, nextPt )
local x, y = math.extrudeToLen( inter, bounce, len )
bounce.velocity = { x=bounce.x-inter.x, y=bounce.y-inter.y }
return success, bounce, {x=x,y=y}, inter -- bounced point, reflected point, intersection
end
math.bouncePointAgainstLine = bouncePointAgainstLine
--[[
Reflects a point off a polygon, either keeping it within or without by checking intersections.
Multiple bounces may be processed with only the last resulting location being returned, this is because the
distance travelled by the 'pt' point is properly calculated.
The original velocity of the 'pt' point will also be maintained and will not decrement because of the last bounce distance.
Parameters:
points: List of points comprising the polygon, assumes closed (auto-joins the first and last points)
pt: The point being fired at/in the polygon. Must contain {x,y,velocity={x,y}}
Returns:
pt: A point matching the definition of the 'pt' parameter, with {x,y,velocity={x,y}} for the resulting position. Nil, if there was no collision detected.
list: List of locations the point bounced from
]]--
local function bouncePointAgainstPolygon( points, pt )
local wp = math.wrapIndex
local function comparePoints( a, b )
return math.round( math.lengthOf( a, b ) ) == 0
end
local function getLine( index )
return { a=points[index], b=points[ wp( index+1, points ) ] }
end
-- get velocity speed
local initspeed = math.lengthOf( {x=0,y=0}, pt.velocity )
-- initialise output list
local intersections = {}
-- perform first iteration...
while (true) do
local a = pt
local b = { x=a.x+a.velocity.x, y=a.y+a.velocity.y }
local found = math.polygonLineIntersection( points, a, b, true ) -- polygonLineIntersection( polygon, a, b, sort, notWrapped )
if (#found > 0 and comparePoints( found[1], a )) then table.remove( found, 1 ) end
if (#found == 0) then print(#intersections); return a, intersections end
local success, bounce, reflect, inter = math.bouncePointAgainstLine( getLine( found[1].lineIndex ), a ) -- success, bounced point, reflected point, intersection
inter.velocity = bounce.velocity
intersections[ #intersections+1 ] = inter
pt = inter
end
end
math.bouncePointAgainstPolygon = bouncePointAgainstPolygon
--[[
Shows that the lines intersect.
Parameters:
a, b: Lines with a and b ends, each end with x,y coords.
a, b, c, d: Line ends for a-b and c-d, each end having x,y coords.
ax, ay, bx, by, cx, cy, dx, dy: Full points of two lines
Returns:
true, x, y: If the lines intersect
false: If the lines do not intersect
Ref:
http://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect
]]--
local function getLineIntersection( ... ) -- , *i_x, *i_y)
local p0_x, p0_y, p1_x, p1_y, p2_x, p2_y, p3_x, p3_y
-- separate parameters
if (#arg == 2) then
p0_x, p0_y, p1_x, p1_y = arg[1].a.x, arg[1].a.y, arg[1].b.x, arg[1].b.y
p2_x, p2_y, p3_x, p3_y = arg[2].a.x, arg[2].a.y, arg[2].b.x, arg[2].b.y
elseif (#arg == 4) then
p0_x, p0_y, p1_x, p1_y = arg[1].x, arg[1].y, arg[2].x, arg[2].y
p2_x, p2_y, p3_x, p3_y = arg[3].x, arg[3].y, arg[4].x, arg[4].y
elseif (#arg == 8) then
p0_x, p0_y, p1_x, p1_y, p2_x, p2_y, p3_x, p3_y = unpack( arg )
end
local i_x, i_y -- output
local s1_x, s1_y, s2_x, s2_y
s1_x = p1_x - p0_x
s1_y = p1_y - p0_y
s2_x = p3_x - p2_x
s2_y = p3_y - p2_y
local s, t
s = (-s1_y * (p0_x - p2_x) + s1_x * (p0_y - p2_y)) / (-s2_x * s1_y + s1_x * s2_y)
t = ( s2_x * (p0_y - p2_y) - s2_y * (p0_x - p2_x)) / (-s2_x * s1_y + s1_x * s2_y)
if (s >= 0 and s <= 1 and t >= 0 and t <= 1) then
-- Collision detected
i_x = p0_x + (t * s1_x)
i_y = p0_y + (t * s1_y)
return true, i_x, i_y
end
return false -- ; // No collision
end
math.getLineIntersection = getLineIntersection
--[[ Used when the tablelib library is not provided. ]]--
local hasTableLib = pcall(function() require("tablelib") end)
if (not hasTableLib and table.range == nil) then
print("Adding table.range()")
table.range = function( tbl, index, size )
if (index == nil or index < 1) then return nil end
size = size or #tbl-index+1
local output = {}
for i=index, index+size-1 do
output[#output+1] = tbl[i]
end
return output
end
end
--[[
Finds a list of intersection points between two paths of { x,y,x,y, ... } format.
Paths must be even-numbered, have more than four values and consist of only x,y values in that order.
The path will be checked for self intersections if the isSelf parameter is true or the path tables are the same table.
Parameters:
aPath: First path
bPath: Second path
isSelf: true if the two path tables are the same path, leave nil otherwise
Returns:
{ {aIndex,bIndex,x,y,aPath,bPath}, ... }
Table of tables containing the a path and b path indices and x,y coordinates of intersection.
]]--
local function getIntersectionsBetween( aPath, bPath, isSelf )
local intersections = {}
local isSelf = isSelf or (aPath == bPath)
for aIndex=1, #aPath-3, 2 do
for bIndex=1, #bPath-3, 2 do
if (not isSelf or (isSelf and bIndex < aIndex-2 or bIndex > aIndex+2)) then
local pts = table.copy( table.range( aPath, aIndex, 4 ), table.range( bPath, bIndex, 4 ) )
local does, x, y = math.getLineIntersection( unpack( pts ) )
if (does) then
local intersection = {
x=x, y=y,
target={ index=aIndex, path=aPath },
other={ index=bIndex, path=bPath },
}
intersections[#intersections+1] = intersection
end
end
end
end
return intersections
end
math.getIntersectionsBetween = getIntersectionsBetween
local function test_getIntersectionsBetween()
local a = {652,116,646,118,639,119,632,121,624,123,615,125,606,127,596,129,586,131,575,133,564,135,553,137,541,139,528,142,516,144,503,147,490,149,476,152,462,155,449,157,435,160,421,163,406,166,392,169,378,172,363,175,349,179,335,182,321,185,307,189,293,192,279,196,266,199,252,203,239,207,227,210,214,214,202,218,190,222,179,226,168,230,158,234,148,239,139,243,130,247,122,252,114,256,107,261,101,265,93,272,86,278,79,285,73,291,67,298,62,306,57,313,52,320,48,328,44,336,40,344,37,352,34,360,31,368,29,377,27,385,26,393,25,402,24,411,24,419,24,428,24,436,25,445,26,453,28,462,30,470,32,479,35,487,38,495,42,503,46,511,50,519,55,527,60,535,66,542,72,549,78,556,85,563,102,577,121,589,144,601,170,612,198,624,229,635,261,646,294,656,329,667,363,677,398,687,433,697,466,707,498,717,529,727,557,737,583,747,606,757,626,767,640,776,647,781,654,786,660,791,666,796,671,801,677,805,682,810,686,815,690,819,694,824,698,828,701,833,704,837,706,842,709,846,710,851,712,855,713,860,714,864,714,869,714,878,713,882,712,887,711,892,709,896,707,901,704,906,702,911,699,916,695,921,691,926,687,931,682,937,677,942,672,948,666,953,660,959,654,965,650,968,643,974,635,980,626,986,616,993,606,1000,595,1007,583,1014,570,1021,557,1029,543,1036,529,1044,514,1052,499,1060,484,1068,468,1076,452,1084,435,1092,419,1100,402,1108,386,1116,369,1124,352,1132,336,1140,319,1148,303,1155,287,1163,271,1170,255,1177,240,1184,225,1191,211,1198,197,1204,183,1210,171,1216,158,1222,147,1227,136,1232,126,1237,117,1242,109,1246,101,1249}
local b = {98,66,103,69,110,73,116,76,124,80,132,84,140,88,150,92,159,96,170,101,180,105,191,110,203,115,215,120,227,125,239,131,252,136,265,142,279,147,292,153,306,159,319,165,333,171,347,177,361,184,375,190,389,197,403,203,417,210,431,217,444,223,458,230,471,237,484,244,496,251,509,259,521,266,533,273,544,280,555,288,565,295,575,302,585,310,594,317,602,325,610,332,617,340,623,347,629,355,634,362,639,370,642,377,645,385,647,392,648,400,647,414,645,423,641,433,635,444,628,454,620,464,610,475,599,486,587,497,574,508,560,519,545,530,530,541,513,553,497,564,479,576,461,587,443,599,424,611,406,622,387,634,368,646,349,658,330,670,312,682,293,693,275,705,258,717,241,729,225,740,209,752,195,763,181,775,168,786,156,798,145,809,136,820,127,831,120,842,115,852,111,863,109,873,108,884,109,894,111,903,115,913,119,923,125,933,131,943,139,952,148,963,157,973,168,983,179,993,191,1003,204,1013,217,1023,231,1033,245,1043,260,1053,275,1063,291,1073,307,1082,324,1092,340,1102,357,1111,374,1121,391,1130,408,1139,425,1148,442,1157,458,1165,475,1174,491,1182,507,1190,523,1198,539,1206,554,1214,568,1221,582,1228,595,1235,608,1241,620,1248,631,1254,642,1259,652,1265,660,1270,668,1275,675,1279}
local aline = display.newLine( unpack( a ) )
aline.strokeWidth = 6
local bline = display.newLine( unpack( b ) )
bline.strokeWidth = 6
local intersections = math.getIntersectionsBetween( a, b )
for i=1, #intersections do
local pt = intersections[i]
local dot = display.newCircle( pt.x, pt.y, 6 )
dot:setFillColor( 1,0,0 )
end
end
--test_getIntersectionsBetween()
local function test_getSelfIntersections( path )
local a = {100,100,107,102,113,103,119,103,125,104,132,104,140,104,147,104,155,104,164,104,172,104,181,104,191,104,200,104,210,104,220,105,225,106,230,106,235,107,240,107,245,108,250,109,255,110,260,111,266,112,271,113,276,115,281,116,287,118,292,120,297,121,302,123,308,126,313,128,318,130,323,133,328,136,333,139,339,142,344,146,349,149,354,153,359,157,364,161,369,165,374,170,378,175,383,180,388,185,392,190,397,196,415,221,432,246,451,274,470,306,490,340,511,376,533,414,554,453,575,493,596,534,615,575,634,616,651,655,666,694,679,731,690,767,699,799,704,829,706,856,705,879,701,899,696,907,692,913,686,920,679,926,672,932,663,937,654,942,643,948,632,952,620,957,608,961,595,965,581,969,567,973,552,976,537,979,521,982,505,984,489,986,472,988,456,990,439,991,422,992,405,993,388,994,371,994,354,994,337,993,321,993,304,992,288,990,272,989,257,987,242,985,228,982,214,980,200,976,188,973,176,969,164,965,154,961,144,956,135,951,127,945,120,940,114,934,109,927,105,921,102,913,100,906,100,900,100,894,101,887,103,880,105,872,108,864,111,855,115,846,120,836,125,826,131,816,137,805,143,794,150,782,158,770,166,758,174,746,183,733,192,720,201,707,211,694,221,680,231,666,242,652,252,638,263,623,275,609,286,594,298,580,310,565,322,550,334,535,346,520,358,505,371,490,383,475,396,460,408,445,421,430,433,415,446,401,458,386,470,372,483,357,495,343,507,329,518,315,530,302,542,289,553,275,564,263,575,250,586,238,596,226,606,214,616,202,625,191,634,181,643,171,651,161,659,151,666,142,673,134,680,125,686,118,692,111,697,104}
local aline = display.newLine( unpack( a ) )
aline.strokeWidth = 6
local intersections = math.getIntersectionsBetween( a, a )
for i=1, #intersections do
local pt = intersections[i]
local dot = display.newCircle( pt.x, pt.y, 4 )
dot:setFillColor( 1,0,0 )
end
end
--test_getSelfIntersections()
-- This is based off an explanation and expanded math presented by Paul Bourke:
-- It takes two lines as inputs and returns true if they intersect, false if they don't.
-- If they do, ptIntersection returns the point where the two lines intersect.
-- params a, b = first line
-- params c, d = second line
-- param ptIntersection: The point where both lines intersect (if they do)
-- http://local.wasp.uwa.edu.au/~pbourke/geometry/lineline2d/
-- http://paulbourke.net/geometry/pointlineplane/
local function doLinesIntersect( a, b, c, d )
-- parameter conversion
local L1 = {X1=a.x,Y1=a.y,X2=b.x,Y2=b.y}
local L2 = {X1=c.x,Y1=c.y,X2=d.x,Y2=d.y}
-- Denominator for ua and ub are the same, so store this calculation
local _d = (L2.Y2 - L2.Y1) * (L1.X2 - L1.X1) - (L2.X2 - L2.X1) * (L1.Y2 - L1.Y1)
-- Make sure there is not a division by zero - this also indicates that the lines are parallel.
-- If n_a and n_b were both equal to zero the lines would be on top of each
-- other (coincidental). This check is not done because it is not
-- necessary for this implementation (the parallel check accounts for this).
if (_d == 0) then
return false
end
-- n_a and n_b are calculated as seperate values for readability
local n_a = (L2.X2 - L2.X1) * (L1.Y1 - L2.Y1) - (L2.Y2 - L2.Y1) * (L1.X1 - L2.X1)
local n_b = (L1.X2 - L1.X1) * (L1.Y1 - L2.Y1) - (L1.Y2 - L1.Y1) * (L1.X1 - L2.X1)
-- Calculate the intermediate fractional point that the lines potentially intersect.
local ua = n_a / _d
local ub = n_b / _d
-- The fractional point will be between 0 and 1 inclusive if the lines
-- intersect. If the fractional calculation is larger than 1 or smaller
-- than 0 the lines would need to be longer to intersect.
if (ua >= 0 and ua <= 1 and ub >= 0 and ub <= 1) then
local x = L1.X1 + (ua * (L1.X2 - L1.X1))
local y = L1.Y1 + (ua * (L1.Y2 - L1.Y1))
return {x=x, y=y}
end
return false
end
math.doLinesIntersect = doLinesIntersect
function math.doesThickLineIntersect( a, b, abw, c, d, cdw )
w = w or 10
-- get rects for the stroked lines
local ab = display.newRect( math.avg(a.x,b.x), math.avg(a.y,b.y), math.lengthOf(a,b), abw )
ab.rotation = math.angleOf( a,b )
local cd = display.newRect( math.avg(c.x,d.x), math.avg(c.y,d.y), math.lengthOf(c,d), cdw )
cd.rotation = math.angleOf( c,d )
-- get path points of the rects
local function getOutline( rect )
local pathA = {}
local x, y = rect:localToContent( -rect.width/2, -rect.height/2 )
pathA[#pathA+1], pathA[#pathA+2] = x, y
x, y = rect:localToContent( rect.width/2, -rect.height/2 )
pathA[#pathA+1], pathA[#pathA+2] = x, y
x, y = rect:localToContent( rect.width/2, rect.height/2 )
pathA[#pathA+1], pathA[#pathA+2] = x, y
x, y = rect:localToContent( -rect.width/2, rect.height/2 )
pathA[#pathA+1], pathA[#pathA+2] = x, y
return pathA
end
local pathA = getOutline( ab )
local pathB = getOutline( cd )
-- remove rects
ab:removeSelf()
cd:removeSelf()
-- get polygon intersection
local p, intersects = math.getPolygonIntersection( pathA, pathB )
if (intersects) then
-- if intersection is returned, return avg x and y values of returned polygon
local x, y = 0, 0
for i=1, #p do
x, y = x+p[i].x, y+p[i].y
end
return { x=x/#p, y=y/#p }
else
-- if intersection is not returned, return false
return false
end
end
local function testDoesThickLineIntersect()
timer.performWithDelay( 100, function()
local pt = math.doesThickLineIntersect(
{x=100,y=100},{x=300,y=100},1,
{x=200,y=100},{x=500,y=100},1
)
if (pt) then
display.newCircle( pt.x, pt.y, 10 ).fill = {0,0,1}
end
end, 1 )
end
--testDoesThickLineIntersect()
--[[
Points defined must define overlapping lines.
Returns the line segment ends where the two lines overlapp.
Parameters:
ax, ay, bx, by, cx, cy, dx, dy: Line ends defined individually
{a={x,y},b={x,y}}, {a={x,y},b={x,y}}: Lines defined individually
Returns:
nil if no parallel lines
{a={x,y},b={x,y}}, true: If the lines are parallel on the x axis
{a={x,y},b={x,y}}, false: If the lines are parallel on the y axis
]]--
function math.getParallelLineOverlap( ... )
local ax, ay, bx, by, cx, cy, dx, dy
if (#arg == 2) then
ax, ay, bx, by = arg[1].a.x, arg[1].a.y, arg[1].b.x, arg[1].b.y
cx, cy, dx, dy = arg[2].a.x, arg[2].a.y, arg[2].b.x, arg[2].b.y
elseif (#arg == 8) then
ax, ay, bx, by, cx, cy, dx, dy = unpack( arg )
end
local a, b, c, d
local xSame
if (ax == bx and ax == cx and ax == dx) then
xSame = true
a, b, c, d = ay, by, cy, dy
elseif (ay == by and ay == cy and ay == dy) then
xSame = false
a, b, c, d = ax, bx, cx, dx
else
return nil
end
local function sortAB( a, b )
return math.min( a, b ), math.max( a, b )
end
a, b = sortAB( a, b )
c, d = sortAB( c, d )
local OverlapInterval = nil
if (b - c >= 0 and d - a >=0 ) then
if (xSame) then
OverlapInterval = { a={ x=ax, y=math.max(a, c) }, b={ x=ax, y=math.min(b, d) } }
else
OverlapInterval = { a={ x=math.max(a, c), y=ay }, b={ x=math.min(b, d), y=ay } }
end
end
return OverlapInterval, xSame
end
local function testGetParallelLineOverlap()
local inner = display.newGroup()
for i=0, 355, 10 do
local pt = math.rotateTo( {x=0,y=-100}, i )
local line = display.newLine( inner, pt.x+display.actualCenterX, pt.y+display.actualCenterY, pt.x*2+display.actualCenterX, pt.y*2+display.actualCenterY )
line.strokeWidth = 5
line.stroke = {0,0,0}
line.points = { pt.x+display.actualCenterX, pt.y+display.actualCenterY, pt.x*2+display.actualCenterX, pt.y*2+display.actualCenterY }
end
local outer = display.newGroup()
outer.x, outer.y = display.actualCenterX, display.actualCenterY
local line = display.newLine( outer, 0, -150, 0, -250 )
line.strokeWidth = 5
line.stroke = {1,0,0}
transition.to( outer, { time=30000, rotation=360 } )
local r = 10
Runtime:addEventListener( "enterFrame", function()
for i=1, inner.numChildren do
local ax, ay, bx, by = math.nearest(inner[i].points[1],r), math.nearest(inner[i].points[2],r), math.nearest(inner[i].points[3],r), math.nearest(inner[i].points[4],r)
local cx, cy = outer:localToContent( 0, -150 )
local dx, dy = outer:localToContent( 0, -250 )
cx, cy, dx, dy = math.nearest(cx,r), math.nearest(cy,r), math.nearest(dx,r), math.nearest(dy,r)
local overlap = math.getParallelLineOverlap( ax, ay, bx, by, cx, cy, dx, dy )
if (overlap) then
local line = display.newLine( overlap.a.x, overlap.a.y, overlap.b.x, overlap.b.y )
line.stroke = {0,.4,1}
line.strokeWidth = 10
end
end
end )
end
--testGetParallelLineOverlap()
-- returns the closest point on the line between A and B from point P
local function GetClosestPoint( A, B, P, segmentClamp )
local AP = { x=P.x - A.x, y=P.y - A.y }
local AB = { x=B.x - A.x, y=B.y - A.y }
local ab2 = AB.x*AB.x + AB.y*AB.y
local ap_ab = AP.x*AB.x + AP.y*AB.y
local t = ap_ab / ab2
if (segmentClamp ~= false) then
if (t < 0.0) then
t = 0.0
elseif (t > 1.0) then
t = 1.0
end
end
local closest = { x=A.x + AB.x * t, y=A.y + AB.y * t }
return closest
end
math.GetClosestPoint = GetClosestPoint
--[[
Returns the closest line and the nearest point on that line.
Parameters:
pt: The point to check againt the polygon.
points: The points of a polygon.
isClosed: True if the first point is the same as the last point (passed in as a closed polygon) otherwise the function will behave as though the last line connects the first and last points.
Returns:
table: A table of the closest lines and the points on those lines which is closest to the 'pt' parameter. Table contains { {pt,len,index}, ... }
]]--
local function closestLineAndPoint( pt, points, isClosed )
local wp = math.wrapIndex
local distances = {}
local function compare( a, b )
return (a.len < b.len)
end
local total = points.numChildren or #points
local adjust = 0
if (isClosed) then
adjust = -1
end
for i=1, total+adjust do
local p = math.GetClosestPoint( points[wp(i,points)], points[wp(i+1,points)], pt )
local len = math.lengthOf( p, pt )
distances[#distances+1] = { pt=p, len=len, index=i }
end
table.sort( distances, compare )
local output = {}
local smallestLen = distances[1].len
local i = 1
while (distances[i].len == smallestLen) do
output[#output+1] = distances[i]
i = i + 1
end
return output
end
math.closestLineAndPoint = closestLineAndPoint
--[[
Circle Line Intersection
Description:
Determines if a line (defined by Ax,Ay-Bx,By) intersects with a circle (defined by Cx,Cy with radius R).
Always returns two points if the intersection is secant (see third output).
Params:
Ax, Ay: Start of the line
Bx, By: End of the line
Cx, Cy: Centre of the circle
R: Radius of the circle
Returns:
"none": The line does not touch the circle.
"tangent", x, y: The line meets the edge of the circle but does not cut into it.
The x,y is the location of the intersection.
"secant", ax, ay, bx, by: The line cuts through the circle.
ax, ay is the first intersection.
bx, by is the second intersection.
Example:
local Ax, Ay, Bx, By, Cx, Cy, R = 100,100 , 200,100 , 200,200 , 110
display.newLine( Ax, Ay, Bx, By ).strokeWidth = 4
display.newCircle( Cx, Cy, R ):setFillColor( 0,0,1,.5 )
display.newCircle( Cx, Cy, 4 ):setFillColor( 1,0,0 )
local intersectType, ax, ay, bx, by, t = math.circleLineIntersection( Ax, Ay, Bx, By, Cx, Cy, R )
if (t == 0) then
print("Intersection at first end")
elseif (t == 1) then
print("Intersection at last end")
elseif (t > 0 and t < 1) then
print("Intersection between line ends")
elseif (t < 0) then
print("Intersection before first end")
elseif (t > 1) then
print("Intersection after last end")
end
if (intersectType == math.none) then
print("none")
elseif (intersectType == math.tangent) then
print("tanget")
display.newCircle( ax, ay, 5 ):setFillColor(0,.5,0)
elseif (intersectType == math.secant) then
print("secant")
display.newCircle( ax, ay, 3 ):setFillColor( 1,.4,.4 )
display.newCircle( bx, by, 3 ):setFillColor( .4,.4,1 )
end
Ref:
http://stackoverflow.com/questions/1073336/circle-line-segment-collision-detection-algorithm
http://mathworld.wolfram.com/Circle-LineIntersection.html
]]--
local function circleLineIntersection( Ax, Ay, Bx, By, Cx, Cy, R )
-- compute the euclidean distance between A and B
local LAB = math.sqrt( (Bx-Ax)*(Bx-Ax)+(By-Ay)*(By-Ay) )
-- compute the direction vector D from A to B
local Dx = (Bx-Ax)/LAB
local Dy = (By-Ay)/LAB
-- Now the line equation is x = Dx*t + Ax, y = Dy*t + Ay with 0 <= t <= 1.
-- compute the value t of the closest point to the circle center (Cx, Cy)
local t = ( Dx*(Cx-Ax) + Dy*(Cy-Ay) )
-- This is the projection of C on the line from A to B.
-- compute the coordinates of the point E on line and closest to C
local Ex = t*Dx+Ax
local Ey = t*Dy+Ay
-- compute the euclidean distance from E to C
local LEC = math.sqrt( (Ex-Cx)*(Ex-Cx)+(Ey-Cy)*(Ey-Cy) )
-- test if the line intersects the circle
if ( LEC < R ) then
-- compute distance from t to circle intersection point
dt = math.sqrt( R*R - LEC*LEC )
-- compute first intersection point
Fx = (t-dt)*Dx + Ax
Fy = (t-dt)*Dy + Ay
-- compute second intersection point
Gx = (t+dt)*Dx + Ax
Gy = (t+dt)*Dy + Ay
return "secant", Fx, Fy, Gx, Gy, t/LAB
-- else test if the line is tangent to circle
elseif ( LEC == R ) then
-- tangent point to circle is E
return "tangent", Ex, Ey, t/LAB
else
-- line doesn't touch circle
return "none"
end
end
math.circleLineIntersection = circleLineIntersection
--[[
Performs unit normalisation of a vector.
Description:
Unit normalising is basically converting the length of a line to be a fraction of 1.0
This function modified the vector value passed in and returns the length as returned by lengthOf()
Note:
Can also be performed like this:
function Normalise(vector)
local x,y = x/(x^2 + y^2)^(1/2), y/(x^2 + y^2)^(1/2)
local unitVector = {x=x,y=y}
return unitVector
end
Ref:
http://www.fundza.com/vectors/normalize/index.html
]]--
local function normalise( vector )
local len = math.lengthOf( vector )
vector.x = vector.x / len
vector.y = vector.y / len
return len
end
math.normalise = normalise
--[[
Polygons
]]--
--[[
Calculates the area of a polygon.
Will not calculate area for self-intersecting polygons (where vertices cross each other)
Parameters:
points: table of {x,y} points or list of {x,y,x,y...} coords
Ref:
http://www.mathopenref.com/coordpolygonarea2.html
]]--
local function polygonArea( points )
if (type(points[1]) == "number") then
points = math.tableToPoints( points )
end
local count = #points
if (points.numChildren) then
count = points.numChildren
end
local area = 0 -- Accumulates area in the loop
local j = count -- The last vertex is the 'previous' one to the first
for i=1, count do
area = area + (points[j].x + points[i].x) * (points[j].y - points[i].y)
j = i -- j is previous vertex to i
end
return math.abs(area/2)
end
math.polygonArea = polygonArea
--[[
Calculates the area of a table of polygons and also returns the sum of the areas.
Overlapping intersecting areas are not accounted for.
Parameters:
polygons: table of polygons - see polygonArea()
Returns:
Table of { polygon, area } tables, sum of areas.
]]--
local function polygonAreas( polygons )
local tbl = {}
local sum = 0
for i=1, #tbl do
local polygon = polygons[i]
local entry = { polygon=polygon, area=polygonArea( polygon ) }
tbl[ #tbl+1 ] = entry
sum = sum + entry.area
end
return tbl, sum
end
math.polygonAreas = polygonAreas
--[[
Returns true if the dot {x,y} is within the polygon defined by points table { {x,y},{x,y},{x,y},... }
Accepts coordinates list {x,y,x,y,...} or points {x,y} table or display group.
Parameters:
points: table of points or list of coordinates of polygon
dot: point to check for being inside or outside the bounds of the polygon
Return:
true if the dot is inside the polygon
]]--
local function isPointInPolygon( points, dot )
local count = points.numChildren
if (count == nil) then
points = math.ensurePointsTable( points )
count = #points
end
local i, j = count, count
local oddNodes = false
for i=1, count do
if ((points[i].y < dot.y and points[j].y>=dot.y
or points[j].y< dot.y and points[i].y>=dot.y) and (points[i].x<=dot.x
or points[j].x<=dot.x)) then
if (points[i].x+(dot.y-points[i].y)/(points[j].y-points[i].y)*(points[j].x-points[i].x)<dot.x) then
oddNodes = not oddNodes
end
end
j = i
end
return oddNodes
end
math.isPointInPolygon = isPointInPolygon
--[[
Return true if the dot { x,y } is within any of the polygons in the list.
Parameters:
polygons: table of polygons
dot: point to check for being inside the polygons
Return:
true if the point is inside any of the polygons, the polygon containing the point
]]--
local function isPointInPolygons( polygons, dot )
for i=1, #polygons do
if (isPointInPolygon( polygons[i], dot )) then
return true, polygons[i]
end
end
return false
end
math.isPointInPolygons = isPointInPolygons
-- Returns true if the points in the polygon wind clockwise
-- Does not consider that the vertices may intersect (lines between points might cross over)
local function isPolygonClockwise( pointList )
local area = 0
if (type(pointList[1]) == "number") then
pointList = math.pointsToTable( pointList )
print("#pointList",#pointList)
end
for i = 1, #pointList-1 do
local pointStart = { x=pointList[i].x - pointList[1].x, y=pointList[i].y - pointList[1].y }
local pointEnd = { x=pointList[i + 1].x - pointList[1].x, y=pointList[i + 1].y - pointList[1].y }
area = area + (pointStart.x * -pointEnd.y) - (pointEnd.x * -pointStart.y)
end
return (area < 0)
end
math.isPolygonClockwise = isPolygonClockwise
local function doPointsWindClockwise( ... )
local area = 0
for i=1, #arg-3, 2 do
local pointStart = { x=arg[i] - arg[1], y=arg[i+1] - arg[2] }
local pointEnd = { x=arg[i + 2] - arg[1], y=arg[i + 3] - arg[2] }
area = area + (pointStart.x * -pointEnd.y) - (pointEnd.x * -pointStart.y)
end
return (area < 0)
end
math.doPointsWindClockwise = doPointsWindClockwise
--[[
local function isWindingClockwise( ... )
local signedArea = 0
for i=1, #arg, 2 do
x1 = point[1]
y1 = point[2]
if point is last point
x2 = firstPoint[1]
y2 = firstPoint[2]
else
x2 = nextPoint[1]
y2 = nextPoint[2]
end
signedArea += (x1 * y2 - x2 * y1)
end
return signedArea / 2
end
math.isWindingClockwise = isWindingClockwise
]]--
--[[
Returns true if the point has less than 180 degrees between the neighbouring points.
Parameters:
point: the {x,y} point to check the angle at
b: the {x,y} point preceding the angle point
c: the {x,y} point following the angle point
Returns:
true if the point's angle is less than 180 degrees.
]]--
local function isPointConcave( a, b, c )
local small = smallestAngleDiff( math.angleOf(b,a), math.angleOf(b,c) )
if (small < 0) then
return false
else
return true
end
end
math.isPointConcave = isPointConcave
--[[
Returns true if the polygon is concave.
Returns nil if there are not enough points ( < 3 )
Can accept a display group.
Parameters:
points: table of {x,y} points or list of {x,y,x,y,...} coords
Returns:
true if the polygon is not convex.
]]--
local function isPolygonConcave( points )
-- is points a display group?
local count = points.numChildren
if (count == nil) then
-- points is not a display group...
-- ensure table of points
points = math.ensurePointsTable( points )
count = #points
end
-- cannot check if input is not a polygon
if (count < 3) then
return nil
end
local isConcave = true
for i=1, count do
if (i == 1) then
isConcave = isPointConcave( points[count],points[1],points[2] )
elseif (i == count) then
isConcave = isPointConcave( points[count-1], points[count],points[1] )
else
isConcave = isPointConcave( points[i-1], points[i], points[i+1] )
end
if (not isConcave) then
return false
end
end
return true
end
math.isPolygonConcave = isPolygonConcave
--[[
Returns list of points where a polygon intersects with the line a,b, sorted by closest first if necessary.
Assumes polygon is standard display format: { x,y,x,y,x,y,x,y, ... }
Parameters:
polygon: table of points in either {x,y,x,y,...} or { {x,y}, ... } format
a, b: ends of the line to check for intersection with the polygon format {x,y}
sort: true to sort the found intersections into order from a to b
notWrapped: True if the first and last points are not the same.
Returns:
Table of intersection points with the polygon line's index {x,y,lineIndex,len}
]]--
local function polygonLineIntersection( polygon, a, b, sort, notWrapped )
polygon = math.ensurePointsTable( polygon )
local wp = math.wrapIndex
local len = polygon.numChildren or #polygon
local wrapAdjust = 0
if (notWrapped == true) then
wrapAdjust = -1
end
local points = {}
local idx, good = wp(len+wrapAdjust+1,polygon)
for i=1, len+wrapAdjust do
local idx, good = wp(i+1,polygon)
local success, pt = math.doLinesIntersect( a, b, polygon[i], polygon[idx] )
if (success) then
pt.lineIndex = i
pt.len = math.lengthOf( a, pt )
points[ #points+1 ] = pt
end
end
if (sort and #points > 1) then
table.sort( points, function(f,g) return f.len < g.len end )
end
return points
end
math.polygonLineIntersection = polygonLineIntersection
--[[
Description:
Calculates the average of all the x's and all the y's and returns the average centre of all points.
Works with a display group or table proceeding { {x,y}, {x,y}, ... }
Params:
pts = list of {x,y} points to get the average middle point from
Returns:
x, y = average centre location of all the points
]]--
local function avgMidPoint( ... )
local pts = arg
local x, y, c = 0, 0, #pts
if (pts.numChildren and pts.numChildren > 0) then c = pts.numChildren end
for i=1, c do
x = x + pts[i].x
y = y + pts[i].y
end
return x/c, y/c
end
math.avgMidPoint = avgMidPoint
--[[
Calculates the middle of a polygon's bounding box - as if drawing a square around the polygon and finding the middle.
Also calculates the width and height of the bounding box.
Parameters:
Polygon coordinates as a table of points, display group or list of coordinates.
Returns:
Centroid (centre) x, y
Bounding box width, height
Notes:
Does not centre the polygon. To do this use: math.centrePolygon
]]--
local function getBoundingCentroid( pts )
pts = math.ensurePointsTable( pts )
local xMin, xMax, yMin, yMax = 100000000, -100000000, 100000000, -100000000
for i=1, #pts do
local pt = pts[i]
if (pt.x < xMin) then xMin = pt.x end
if (pt.x > xMax) then xMax = pt.x end
if (pt.y < yMin) then yMin = pt.y end
if (pt.y > yMax) then yMax = pt.y end
end
local width, height = xMax-xMin, yMax-yMin
local cx, cy = xMin+(width/2), yMin+(height/2)
local output = {
centroid = { x=cx, y=cy },
width = width,
height = height,
bounding = { xMin=xMin, xMax=xMax, yMin=yMin, yMax=yMax },
}
return output
end
math.getBoundingCentroid = getBoundingCentroid
--[[
Produces an adjusted polygon so that the vertices are centred on the bounding centroid (square bounding box middle.)
Parameters:
List of coordinates, display group or table of points of the polygon.
cx, cy: Optional centre to focus on
Returns:
Table of points for the adjusted polygon.
x, y of the centre of the polygon.
Notes:
Centres the polygon around the provided point or calculated midpoint. To avoid this use: math.getBoundingCentroid
]]--
local function centerPoly( pts, cx, cy )
local count, islist
pts, count, islist = math.ensurePointsTable( pts )
local output = {}
local x, y
local minx, maxx, miny, maxy = 100000000, -100000000, 100000000, -100000000
-- get dimensions
for i=1, #pts do
x, y = pts[i].x, pts[i].y
if (x < minx) then minx = x end
if (x > maxx) then maxx = x end
if (y < miny) then miny = y end
if (y > maxy) then maxy = y end
end
-- get bounds
local width, height = maxx-minx, maxy-miny
-- get centre
if (cx and cy) then
x, y = cx, cy
else
x, y = minx+(width/2), miny+(height/2)
end
-- centre the polygon around the midpoint
for i=1, #pts do
output[#output+1] = {x=pts[i].x-x,y=pts[i].y-y}
end
if (islist) then
output = math.pointsToTable( output )
end
return output, { x=x, y=y, width=width, height=height, minx=minx, miny=miny, maxx=maxx, maxy=maxy }
end
math.centrePolygon = centerPoly
math.centerPolygon = centerPoly
local function centerPolyForOutline( pts )
local points, tbl = centerPoly( pts )
return centerPoly( pts, tbl.minx, tbl.miny )
end
math.centerPolyForOutline = centerPolyForOutline
--[[
Scales a path or {x,y,x,y,...} points.
Parameters:
path: path of points as accepted by newLine and newPolygon
scale: amount to multiple the values in the path by
Returns:
A table of {x,y,x,y,...} values each simply multiplied by the scale parameter.
]]--
local function scalePath( path, scale )
local p = {}
for i=1, #path do
p[i] = scale * path[i]
end
return p
end
math.scalePath = scalePath
--[[
Description:
Calculates the average of all the x's and all the y's and returns the average centre of all points.
Works with a table proceeding {x,y,x,y,...} as used with display.newLine or physics.addBody
Params:
pts = table of x,y values in sequence
Returns:
x, y = average centre location of all points
]]--
local function midPointOfShape( pts )
local x, y, c, t = 0, 0, #pts, #pts/2
for i=1, c-1, 2 do
x = x + pts[i]
y = y + pts[i+1]
end
return x/t, y/t
end
math.midPointOfShape = midPointOfShape
--[[
Description:
Takes two polygons of the form {{x,y},{x,y},...} and determines if they intersect.
Accepts parameters as display groups, tables of points {x,y} or lists of coords {x,y,x,y,...}
Parameters:
subjectPolygon: first polygon to intersect with the second
clipPolygon: second polygon to intersect with the first
Returns:
Polygon of points of intersection between the two input polygons.
True if the two do intersect, false if they are not touching.
Example:
subjectPolygon = {{x=50, y=150}, {x=200, y=50}, {x=350, y=150}, {x=350, y=300}, {x=250, y=300}, {x=200, y=250}, {x=150, y=350}, {x=100, y=250}, {x=100, y=200}}
clipPolygon = {{x=100, y=100}, {x=300, y=100}, {x=300, y=300}, {x=100, y=300}}
outputList, intersects = clip(subjectPolygon, clipPolygon)
Ref:
http://rosettacode.org/wiki/Sutherland-Hodgman_polygon_clipping#Lua
]]--
local function getPolygonIntersection( subjectPolygon, clipPolygon )
subjectPolygon = math.copyToPointsTable( subjectPolygon )
clipPolygon = math.copyToPointsTable( clipPolygon )
local function inside(p, cp1, cp2)
return (cp2.x-cp1.x)*(p.y-cp1.y) > (cp2.y-cp1.y)*(p.x-cp1.x)
end
local function intersection(cp1, cp2, s, e)
local dcx, dcy = cp1.x-cp2.x, cp1.y-cp2.y
local dpx, dpy = s.x-e.x, s.y-e.y
local n1 = cp1.x*cp2.y - cp1.y*cp2.x
local n2 = s.x*e.y - s.y*e.x
local n3 = 1 / (dcx*dpy - dcy*dpx)
local x = (n1*dpx - n2*dcx) * n3
local y = (n1*dpy - n2*dcy) * n3
return {x=x, y=y}
end
local outputList = subjectPolygon
local cp1 = clipPolygon[#clipPolygon]
for _, cp2 in ipairs(clipPolygon) do -- WP clipEdge is cp1,cp2 here
local inputList = outputList
outputList = {}
local s = inputList[#inputList]
for _, e in ipairs(inputList) do
if inside(e, cp1, cp2) then
if not inside(s, cp1, cp2) then
outputList[#outputList+1] = intersection(cp1, cp2, s, e)
end
outputList[#outputList+1] = e
elseif inside(s, cp1, cp2) then
outputList[#outputList+1] = intersection(cp1, cp2, s, e)
end
s = e
end
cp1 = cp2
end
return outputList, #outputList > 0
end
math.getPolygonIntersection = getPolygonIntersection
--[[
Products
]]--
--[[
Calculates the dot product of two lines.
This function implements the simple form of the dot product calculation: a · b = ax × bx + ay × by
The lines can be provided in 3 forms:
Parameters:
a: {x,y}
b: {x,y}
Example:
print( dotProduct( {x=10,y=10}, {x=-10,y=10} ) )
Parameters:
a: {a,b}
b: {a,b}
Example:
print( dotProduct(
{ a={x=10,y=10}, b={x=101,y=5} },
{ a={x=10,y=-10}, b={x=51,y=10} }
))
Params:
lenA: Length A
lenB: Length B
deg: Angle between points A and B in degrees
Example:
print( dotProduct( 23, 10, 90 ) )
Ref:
http://www.mathsisfun.com/algebra/vectors-dot-product.html
http://members.tripod.com/c_carleton/dotprod.html/
http://www.mathsisfun.com/algebra/vector-calculator.html
]]--
local function dotProduct( ... )
local ax, ax, bx, by
if (#arg == 2 and arg[1].a == nil) then
-- two vectors - get the vectors
ax, ay = arg[1].x, arg[1].y
bx, by = arg[2].x, arg[2].y
elseif (#arg == 2 and a.x == nil) then
-- two lines - calculate the vectors
ax = arg[1].b.x - arg[1].a.x
ay = arg[1].b.y - arg[1].a.y
bx = arg[2].b.x - arg[2].a.x
by = arg[2].b.y - arg[2].a.y
elseif (#arg == 3 and type(arg[1]) == "number") then
-- two lengths and an angle: lenA * lenB * math.cos( deg )
return arg[1] * arg[2] * math.cos( arg[3] )
elseif (#arg == 4 and type(arg[1]) == "number") then
-- two lines, params are (x,y,x,y) - get the vectors
ax, ay = arg[1], arg[2]
bx, by = arg[3], arg[4]
end
-- multiply the x's, multiply the y's, then add
local dot = ax * bx + ay * by
return dot
end
math.dotProduct = dotProduct
--[[
Description:
Calculates the cross product of a vector.
Ref:
http://www.math.ntnu.no/~stacey/documents/Codea/Library/Vec3.lua
]]--
local function crossProduct( a, b )
local x, y, z
x = a.y * (b.z or 0) - (a.z or 0) * b.y
y = (a.z or 0) * b.x - a.x * (b.z or 0)
z = a.x * b.y - a.y * b.x
return { x=x, y=y, z=z }
end
math.crossProduct = crossProduct
--[[
Description:
Perform the cross product on two vectors. In 2D this produces a scalar.
Params:
a: {x,y}
b: {x,y}
Ref:
http://www.iforce2d.net/forums/viewtopic.php?f=4&t=79&sid=b9ecd62533361594e321de04b3929d4f
]]--
local function b2CrossVectVect( a, b )
return a.x * b.y - a.y * b.x;
end
math.b2CrossVectVect = b2CrossVectVect
--[[
Description:
Perform the cross product on a vector and a scalar. In 2D this produces a vector.
Params:
a: {x,y}
b: float
Ref:
http://www.iforce2d.net/forums/viewtopic.php?f=4&t=79&sid=b9ecd62533361594e321de04b3929d4f
]]--
local function b2CrossVectFloat( a, s )
return { x = s * a.y, y = -s * a.x }
end
math.b2CrossVectFloat = b2CrossVectFloat
--[[
Description:
Perform the cross product on a scalar and a vector. In 2D this produces a vector.
Params:
a: float
b: {x,y}
Ref:
http://www.iforce2d.net/forums/viewtopic.php?f=4&t=79&sid=b9ecd62533361594e321de04b3929d4f
]]--
local function b2CrossFloatVect( s, a )
return { x = -s * a.y, y = s * a.x }
end
math.b2CrossFloatVect = b2CrossFloatVect
--[[
Point Collections
]]--
--[[
Wraps table index addresses to keep the index value within the valid table index range.
Eg: Where index -1 and list length 10 function returns 9, true
Eg: Where index 11 and list length 10 function returns 1, true
eg: Where index 5 and list is empty function returns 5, false
Parameters:
index: The table index to be addressed (1-based index)
list: Table or display group to determine the real index of
Returns:
Valid index into the table/display group or original index if list has no content
True if valid index value returned, false if the list/display group is empty
Note:
An extra small function name has been added 'math.wp' to avoid unsightly code when addressing tables.
]]--
local function wrapIndex( index, list )
local len = list.numChildren or #list
if (len == 0) then
return index, false
elseif (index > len) then
return index-len, true
elseif (index < 1) then
return len+index, true
end
return index, true
end
math.wrapIndex = wrapIndex
math.wp = wrapIndex
-- converts a table of {x,y,x,y,...} to points {x,y}
local function tableToPoints( tbl )
local pts = {}
for i=1, #tbl-1, 2 do
pts[#pts+1] = { x=tbl[i], y=tbl[i+1] }
end
return pts
end
math.tableToPoints = tableToPoints
-- converts a list of points {x,y} to a table of coords {x,y,x,y,...}
local function pointsToTable( pts )
local tbl = {}
for i=1, #pts do
tbl[#tbl+1] = pts[i].x
tbl[#tbl+1] = pts[i].y
end
return tbl
end
math.pointsToTable = pointsToTable
-- ensures that a list of coordinates is converted to a table of {x,y} points
-- returns a table of {x,y} points, the number of points, whether a list or not
local function ensurePointsTable( tbl )
if (type(tbl[1]) == "number") then
-- list contains {x,y,x,y,...} coordinates - convert to table of {x,y}
tbl = tableToPoints( tbl )
return tbl, #tbl, true
else
-- table is already in {x,y} point format...
-- check for display group
local count = tbl.numChildren
if (count == nil) then
count = #tbl
end
return tbl, count, false
end
end
math.ensurePointsTable = ensurePointsTable
-- copies the points from a list of coords, table of points or display group into a new table of {x,y} points
local function copyToPointsTable( points )
local tbl = {}
local count = points.numChildren
if (count == nil) then
count = #points
end
local isCoords = (type(points[1]) == "number")
local step = 1
if (isCoords) then
step = 2
end
for i=1, count, step do
if (isCoords) then
tbl[#tbl+1] = {x=points[i],y=points[i+1]}
else
tbl[#tbl+1] = {x=points[i].x,y=points[i].y}
end
end
return tbl
end
math.copyToPointsTable = copyToPointsTable
--[[
Removes points in found in sequence at the same location.
Eg: two points both at {x=10,y=21} will be deduped to just one point.
Parameters:
points: The list of points - can be list of coords but will convert to points on return.
maxdist: If nil the points are directly compared. If provided, points will be deduped if they are closer than this distance.
Returns:
List of points where no two points exist at the same location.
Comments:
Requires the table.lua library file: https://gist.github.com/HoraceBury/9307117
]]--
local function dedupePoints( points, maxdist )
local count, converted
-- ensure points list not coords
points, count, converted = math.ensurePointsTable( points )
-- create output list and copy first point
local pts = {}
pts[1] = points[1]
-- compare points from second to last against previous
for i=2, #points do
if (maxdist) then
if (math.lengthOf( pts[#pts], points[i] ) > maxdist) then
pts[#pts+1] = points[i]
end
else
if (not table.compare( pts[#pts], points[i] )) then
pts[#pts+1] = points[i]
end
end
end
-- compare first and last points
if (maxdist) then
if (math.lengthOf( pts[1], pts[#pts] ) <= maxdist) then
pts[#pts] = nil
end
else
if (table.compare( pts[1], pts[#pts] )) then
pts[#pts] = nil
end
end
if (converted) then
return pointsToTable( pts )
else
return pts
end
end
math.dedupePoints = dedupePoints
local function generateCurve( easingFunction, xStart, yStart, xEnd, yEnd )
easingFunction = easingFunction or easing.inOutQuint
local duration = xEnd-xStart
local path = {}
for x=0, duration do
table.append( path, xStart+x, easingFunction( x, duration, yStart, yEnd-yStart ) )
end
return path
end
math.generateCurve = generateCurve
-- math lib demos
-- demonstrations of each function in the mathlib extension library
-- Please be aware that the composer is broken for this file in daily builds 2014.2196 and above.
display.setStatusBar( display.HiddenStatusBar )
local composer = require( "composer" )
require("mathlib")
local main = { scenes={}, functions={} }
main.list = {
{ label="math.lengthOf = function( ptA, ptB )", scene="lengthOf" },
{ label="math.angleOf = function( centre, ptA, ptB )", scene="angleOf" },
{ label="math.isPointInAngle = function( centre, first, second, point )", scene="isPointInAngle" },
{ label="math.angleBetweenLines( lineA, lineB )", scene="angleBetweenLines" },
{ label="math.getLineIntersection = function( lineA, lineB )", scene="getLineIntersection" },
{ label="math.reflectPointAcrossLine = function( line, pt )", scene="reflectPointAcrossLine" },
{ label="math.polygonArea = function( points )", scene="polygonArea" },
{ label="math.isPolygonConcave = function( points )", scene="isPolygonConcave" },
{ label="math.isPointInPolygon = function( points )", scene="isPointInPolygon" },
{ label="math.getPolygonIntersection = function( subject, clip )", scene="getPolygonIntersection" },
{ label="math.closestLineAndPoint = function( pt, points )", scene="closestLineAndPoint" },
{ label="math.closestPolygonIntersection = function( a, b, polygon )", scene="closestPolygonIntersection" },
{ label="math.bouncePointAgainstLine = function( line, pt )", scene="bouncePointAgainstLine" },
{ label="math.bouncePointAgainstPolygon = function( points, pt )", scene="bouncePointAgainstPolygon" },
}
--[[ Back button ]]--
local back = display.newCircle( display.actualContentWidth-50, 50, 25 )
function back:touch(e)
if (e.phase == "ended") then
composer.gotoScene( "menu", { effect="fade", time=500 } )
end
return true
end
back:addEventListener( "touch", back )
back:addEventListener( "tap", function(e) return true end )
back.alpha = 0
--[[ Supporting functions ]]--
local function newDragSpot( parent, x, y, c, r, callback, label )
local group = display.newGroup()
parent:insert( group )
group.x, group.y = x, y
group.canDelete = false
group.spot = display.newCircle( group, 0, 0, r or 30 )
group.spot.fill = c or {1,0,0}
if (label) then
display.newText{ parent=group, text=label, fontSize=20, x=0, y=0 }
end
function group:touch(e)
e.target.x, e.target.y = e.x, e.y
if (callback) then callback( e.target ) end
if (e.phase == "began") then
display.getCurrentStage():setFocus( e.target )
e.target.hasFocus = true
return true
elseif (e.target.hasFocus) then
if (e.phase == "moved") then
else
e.target.hasFocus = false
display.getCurrentStage():setFocus( nil )
end
return true
end
return false
end
group:addEventListener( "touch", group )
function group:stopTouch()
group:removeEventListener( "touch", group )
end
function group:tap(e)
if (group.canDelete) then
group:removeSelf()
callback()
end
return true
end
group:addEventListener("tap",group)
return group
end
main.functions.newDragSpot = newDragSpot
local function newLine( parent, ax, ay, bx, by, callback, labelA, labelB, radiusA, radiusB, colourA, colourB )
local group = display.newGroup()
parent:insert( group )
local a, b = nil, nil
local line = display.newLine( group, ax, ay, bx, by )
line.strokeWidth = 4
line.stroke = {0,0,1}
function group:update( ax, ay, bx, by )
line:removeSelf()
line = display.newLine( group, ax, ay, bx, by )
group:insert( 1, line )
line.strokeWidth = 4
line.stroke = {0,0,1}
a.x, a.y = ax, ay
b.x, b.y = bx, by
end
local function update( ax, ay, bx, by )
group:update( ax, ay, bx, by )
callback( group )
end
a = main.functions.newDragSpot( group, ax, ay, colourA or {1,0,0}, radiusA or 30, function(e) update( a.x, a.y, b.x, b.y ) end, labelA )
b = main.functions.newDragSpot( group, bx, by, colourB or {1,0,0}, radiusB or 30, function(e) update( a.x, a.y, b.x, b.y ) end, labelB )
group.a, group.b = a, b
return group
end
main.functions.newLine = newLine
local function newPolygon( parent, callback, allowAdd, isClosed )
local group = display.newGroup()
parent:insert( group )
local background = display.newRect( group, display.actualContentWidth/2, display.actualContentHeight/2, display.actualContentWidth, display.actualContentHeight )
background.fill = {0,0,0,0}
background.isHitTestable = true
local lines, spots = display.newGroup(), display.newGroup()
group:insert( lines )
group:insert( spots )
group.lines, group.spots = lines, spots
function group:update(target)
while (lines.numChildren > 0) do
lines[1]:removeSelf()
end
for i=2, spots.numChildren do
local line = display.newLine( lines, spots[i-1].x, spots[i-1].y, spots[i].x, spots[i].y )
line.strokeWidth = 4
line.stroke = {0,0,1}
end
if ((isClosed == nil or isClosed == true) and spots.numChildren > 1) then
local line = display.newLine( lines, spots[1].x, spots[1].y, spots[spots.numChildren].x, spots[spots.numChildren].y )
line.strokeWidth = 4
line.stroke = {0,0,1}
end
callback( group )
end
function group:tap(e)
local spot = main.functions.newDragSpot( spots, e.x, e.y, {1,0,0}, 30, function(e) group:update(e) end )
spot.canDelete = true
group:update()
return true
end
if (allowAdd == nil or allowAdd == true) then
background:addEventListener("tap",group)
else
background.alpha = 0
end
return group
end
main.functions.newPolygon = newPolygon
--[[ Menu ]]--
local function menu()
local scene = composer.newScene("menu")
local function touch(e)
if (e.phase == "ended") then
composer.gotoScene( e.target.item.scene, { effect="fade", time=500 } )
end
return true
end
function scene:create( event )
local sceneGroup = self.view
-- Called when the scene is still off screen (but is about to come on screen)
for i=1, #main.list do
local button = display.newText{ parent=sceneGroup, x=20, y=i*30, text=main.list[i].label, fontSize=20 }
button.x = 20 + button.width/2
button.item = main.list[i]
button:addEventListener("touch",touch)
print(button.text)
end
end
function scene:show( event )
local phase = event.phase
if (phase == "will") then
transition.to( back, { time=500, alpha=0 } )
end
end
function scene:hide( event )
local phase = event.phase
if (phase == "will") then
transition.to( back, { time=500, alpha=1 } )
end
end
scene:addEventListener( "create", scene )
scene:addEventListener( "show", scene )
scene:addEventListener( "hide", scene )
return scene
end
main.scenes.menu = menu
main.scenes.menu()
--[[ lengthOf ]]--
local function lengthOf()
local scene = composer.newScene("lengthOf")
local line = nil
local label = nil
local function update( target )
print(target.a, target.b)
local len = math.lengthOf( target )
label.text = len
end
function scene:create( event )
local sceneGroup = self.view
-- Called when the scene is still off screen (but is about to come on screen).
local button = display.newText{ parent=sceneGroup, x=20, y=20, text="math.lengthOf( ptA, ptB )", fontSize=20 }
button.x = 20 + button.width/2
button.fill = {.5,.75,1}
label = display.newText{ parent=sceneGroup, x=display.actualContentWidth/2, y=60, text="", fontSize=20 }
line = main.functions.newLine( sceneGroup, 100, 100, 400, 100, update )
end
scene:addEventListener( "create", scene )
return scene
end
main.scenes.lengthOf = lengthOf
main.scenes.lengthOf()
--[[ angleOf( centre, ptA, ptB ) ]]--
local function angleOf()
local scene = composer.newScene("angleOf")
local lineA, lineB = nil, nil
local labelA, labelB, labelC = nil, nil, nil
local function update( target )
lineA:update( lineB.a.x, lineB.a.y, lineA.b.x, lineA.b.y )
lineB:update( lineB.a.x, lineB.a.y, lineB.b.x, lineB.b.y )
local a = math.angleOf( lineA.a )
local b = math.angleOf( lineA.a, lineA.b )
local c = math.angleOf( lineA.a, lineA.b, lineB.b )
labelA.text = "Angle at A from (0,0): "..a
labelB.text = "Angle of B from A: "..b
labelC.text = "Angle between B and C at A: "..c
end
function scene:create( event )
local sceneGroup = self.view
-- Called when the scene is still off screen (but is about to come on screen).
local button = display.newText{ parent=sceneGroup, x=20, y=20, text="math.angleOf( centre, ptA, ptB )", fontSize=20 }
button.x = 20 + button.width/2
button.fill = {.5,.75,1}
labelA = display.newText{ parent=sceneGroup, x=display.actualContentWidth/2, y=60, text="", fontSize=20 }
labelB = display.newText{ parent=sceneGroup, x=display.actualContentWidth/2, y=90, text="", fontSize=20 }
labelC = display.newText{ parent=sceneGroup, x=display.actualContentWidth/2, y=120, text="", fontSize=20 }
lineA = main.functions.newLine( sceneGroup, 100, 150, 400, 100, update, "A", "B" )
lineB = main.functions.newLine( sceneGroup, 100, 150, 400, 200, update, "A", "C" )
end
scene:addEventListener( "create", scene )
return scene
end
main.scenes.angleOf = angleOf
main.scenes.angleOf()
--[[ isPointInAngle( centre, first, second, point ) ]]--
local function isPointInAngle()
local scene = composer.newScene("isPointInAngle")
local lineA, lineB = nil, nil
local point = nil
local label = nil
local function update( target )
lineA:update( lineB.a.x, lineB.a.y, lineA.b.x, lineA.b.y )
lineB:update( lineB.a.x, lineB.a.y, lineB.b.x, lineB.b.y )
if (math.isPointInAngle( lineA.a, lineA.b, lineB.b, point )) then
label.text = "Point is in Angle"
label.fill = {0,1,0}
label.x = display.contentCenterX
else
label.text = "Point is NOT in Angle"
label.fill = {1,0,0}
label.x = display.contentCenterX
end
end
function scene:create( event )
local sceneGroup = self.view
-- Called when the scene is still off screen (but is about to come on screen).
local button = display.newText{ parent=sceneGroup, x=20, y=20, text="math.isPointInAngle( centre, first, second, point )", fontSize=20 }
button.x = 20 + button.width/2
button.fill = {.5,.75,1}
label = display.newText{ parent=sceneGroup, x=display.actualContentWidth/2, y=60, text="", fontSize=20 }
lineA = main.functions.newLine( sceneGroup, 100, 150, 400, 100, update, "A", "B" )
lineB = main.functions.newLine( sceneGroup, 100, 150, 400, 200, update, "A", "C" )
point = main.functions.newDragSpot( sceneGroup, 250, 100, {0,1,0}, 30, update, "" )
end
scene:addEventListener( "create", scene )
return scene
end
main.scenes.isPointInAngle = isPointInAngle
main.scenes.isPointInAngle()
--[[ angleBetweenLines ]]--
local function angleBetweenLines()
local scene = composer.newScene("angleBetweenLines")
local lineA, lineB = nil, nil
local label = nil
local function update( target )
local angle = math.angleBetweenLines( lineA, lineB )
label.text = angle
end
function scene:create( event )
local sceneGroup = self.view
-- Called when the scene is still off screen (but is about to come on screen).
local button = display.newText{ parent=sceneGroup, x=20, y=20, text="math.angleBetweenLines( lineA, lineB )", fontSize=20 }
button.x = 20 + button.width/2
button.fill = {.5,.75,1}
label = display.newText{ parent=sceneGroup, x=display.actualContentWidth/2, y=60, text="", fontSize=20 }
lineA = main.functions.newLine( sceneGroup, 100, 100, 400, 100, update )
lineB = main.functions.newLine( sceneGroup, 100, 200, 400, 200, update )
end
scene:addEventListener( "create", scene )
return scene
end
main.scenes.angleBetweenLines = angleBetweenLines
main.scenes.angleBetweenLines()
--[[ math.getLineIntersection = function( a, b, c, d ) ]]--
local function getLineIntersection()
local scene = composer.newScene("getLineIntersection")
local polygon = nil
local lineA = nil
local dots = nil
local function update( target )
local intersected = 0
for i=1, polygon.spots.numChildren-1 do
local lineB = { a=polygon.spots[i], b=polygon.spots[i+1] }
local dointersect, x, y = math.getLineIntersection( lineA, lineB )
if (dointersect) then
intersected = intersected + 1
if (dots[intersected] == nil) then
display.newCircle( dots, 0, 0, 5 )
end
local dot = dots[intersected]
dot.alpha = 1
dot.x, dot.y = x, y
end
end
for i=intersected+1, dots.numChildren do
dots[i].alpha = 0
end
end
function scene:create( event )
local sceneGroup = self.view
-- Called when the scene is still off screen (but is about to come on screen).
local button = display.newText{ parent=sceneGroup, x=20, y=20, text="math.getLineIntersection( lineA, lineB )", fontSize=20 }
button.x = 20 + button.width/2
button.fill = {.5,.75,1}
lineA = main.functions.newLine( sceneGroup, 100, 100, 400, 100, update )
polygon = main.functions.newPolygon( sceneGroup, update, nil, false )
dots = display.newGroup()
sceneGroup:insert( dots )
polygon:tap( {x=100, y=200} )
polygon:tap( {x=400, y=200} )
end
scene:addEventListener( "create", scene )
return scene
end
main.scenes.getLineIntersection = getLineIntersection
main.scenes.getLineIntersection()
--[[ math.reflectPointAcrossLine = function( line, pt ) ]]--
local function reflectPointAcrossLine()
local scene = composer.newScene("reflectPointAcrossLine")
local line = nil
local ptA, ptB = nil, nil
local function update( target )
if (target == ptB) then
local pt = math.reflectPointAcrossLine( line, ptB )
ptA.x, ptA.y = pt.x, pt.y
else
local pt = math.reflectPointAcrossLine( line, ptA )
ptB.x, ptB.y = pt.x, pt.y
end
end
function scene:create( event )
local sceneGroup = self.view
-- Called when the scene is still off screen (but is about to come on screen).
local button = display.newText{ parent=sceneGroup, x=20, y=20, text="math.reflectPointAcrossLine( line, pt )", fontSize=20 }
button.x = 20 + button.width/2
button.fill = {.5,.75,1}
line = main.functions.newLine( sceneGroup, 100, 200, 400, 200, update )
ptA = main.functions.newDragSpot( sceneGroup, 250, 100, {0,1,0}, 30, update, "" )
ptB = main.functions.newDragSpot( sceneGroup, 250, 300, {0,0,1}, 30, update, "" )
end
scene:addEventListener( "create", scene )
return scene
end
main.scenes.reflectPointAcrossLine = reflectPointAcrossLine
main.scenes.reflectPointAcrossLine()
--[[ polygonArea( points ) ]]--
local function polygonArea()
local scene = composer.newScene("polygonArea")
local polygon = nil
local label = nil
local function update( target )
local a = math.polygonArea( polygon.spots )
label.text = a
end
function scene:create( event )
local sceneGroup = self.view
-- Called when the scene is still off screen (but is about to come on screen).
local button = display.newText{ parent=sceneGroup, x=20, y=20, text="math.polygonArea( points )", fontSize=20 }
button.x = 20 + button.width/2
button.fill = {.5,.75,1}
polygon = main.functions.newPolygon( sceneGroup, update )
label = display.newText{ parent=sceneGroup, x=display.actualContentWidth/2, y=60, text="", fontSize=20 }
end
scene:addEventListener( "create", scene )
return scene
end
main.scenes.polygonArea = polygonArea
main.scenes.polygonArea()
--[[ isPolygonConcave( points ) ]]--
local function isPolygonConcave()
local scene = composer.newScene("isPolygonConcave")
local polygon = nil
local label = nil
local function update( target )
local a = math.isPolygonConcave( polygon.spots )
if (a) then
label.text = "Concave"
else
label.text = "Convex"
end
end
function scene:create( event )
local sceneGroup = self.view
-- Called when the scene is still off screen (but is about to come on screen).
local button = display.newText{ parent=sceneGroup, x=20, y=20, text="math.isPolygonConcave( points )", fontSize=20 }
button.x = 20 + button.width/2
button.fill = {.5,.75,1}
polygon = main.functions.newPolygon( sceneGroup, update )
label = display.newText{ parent=sceneGroup, x=display.actualContentWidth/2, y=60, text="", fontSize=20 }
end
scene:addEventListener( "create", scene )
return scene
end
main.scenes.isPolygonConcave = isPolygonConcave
main.scenes.isPolygonConcave()
--[[ isPointInPolygon( points ) ]]--
local function isPointInPolygon()
local scene = composer.newScene("isPointInPolygon")
local polygon = nil
local label = nil
local point = nil
local function update()
local a = math.isPointInPolygon( polygon.spots, point )
if (a) then
label.text = "Inside"
else
label.text = "Outside"
end
end
function scene:create( event )
local sceneGroup = self.view
-- Called when the scene is still off screen (but is about to come on screen).
local button = display.newText{ parent=sceneGroup, x=20, y=20, text="math.isPointInPolygon( points )", fontSize=20 }
button.x = 20 + button.width/2
button.fill = {.5,.75,1}
polygon = main.functions.newPolygon( sceneGroup, update )
point = main.functions.newDragSpot( sceneGroup, display.actualContentWidth/2, display.actualContentHeight/2, {0,1,0}, 30, update )
label = display.newText{ parent=sceneGroup, x=display.actualContentWidth/2, y=60, text="", fontSize=20 }
end
scene:addEventListener( "create", scene )
return scene
end
main.scenes.isPointInPolygon = isPointInPolygon
main.scenes.isPointInPolygon()
--[[ getPolygonIntersection( ... ) ]]--
local function getPolygonIntersection()
local scene = composer.newScene("getPolygonIntersection")
local polygonA, polygonB = nil, nil
local overlay = nil
local intersect = nil
local dot = nil
local function update()
local intersect = math.getPolygonIntersection( polygonA.spots, polygonB.spots )
while (overlay.numChildren > 0) do
overlay[1]:removeSelf()
end
if (#intersect > 3) then
-- get dimensions of polygon's bounding box
local centroid = math.getBoundingCentroid( intersect )
-- adjust vertices of polygon to be centred around the centre of the polygon's bounding box
local polygon, bounds = math.centrePolygon( intersect )
-- render polygon fill
display.newPolygon( overlay, bounds.x, bounds.y, math.pointsToTable( intersect ) ).fill = {0,1,0,.5}
-- render centroid point
display.newCircle( overlay, centroid.centroid.x, centroid.centroid.y, 5 )
-- render white outline, line for line
for i=1, #polygon-1 do
local line = display.newLine( overlay, polygon[i].x+centroid.centroid.x, polygon[i].y+centroid.centroid.y, polygon[i+1].x+centroid.centroid.x, polygon[i+1].y+centroid.centroid.y )
line.stroke = {1,1,1}
line.strokeWidth = 3
end
local line = display.newLine( overlay, polygon[1].x+centroid.centroid.x, polygon[1].y+centroid.centroid.y, polygon[#polygon].x+centroid.centroid.x, polygon[#polygon].y+centroid.centroid.y )
line.stroke = {1,1,1}
line.strokeWidth = 3
end
end
function scene:create( event )
local sceneGroup = self.view
-- Called when the scene is still off screen (but is about to come on screen).
local button = display.newText{ parent=sceneGroup, x=20, y=20, text="math.getPolygonIntersection( subject, clip )", fontSize=20 }
button.x = 20 + button.width/2
button.fill = {.5,.75,1}
polygonA = main.functions.newPolygon( sceneGroup, update, false )
polygonB = main.functions.newPolygon( sceneGroup, update, false )
overlay = display.newGroup()
sceneGroup:insert( overlay )
polygonA:tap( {x=display.actualContentWidth*.3, y=display.actualContentHeight*.3} )
polygonA:tap( {x=display.actualContentWidth*.4, y=display.actualContentHeight*.3} )
polygonA:tap( {x=display.actualContentWidth*.45, y=display.actualContentHeight*.4} )
polygonA:tap( {x=display.actualContentWidth*.45, y=display.actualContentHeight*.5} )
polygonA:tap( {x=display.actualContentWidth*.4, y=display.actualContentHeight*.6} )
polygonA:tap( {x=display.actualContentWidth*.3, y=display.actualContentHeight*.6} )
polygonA:tap( {x=display.actualContentWidth*.25, y=display.actualContentHeight*.5} )
polygonA:tap( {x=display.actualContentWidth*.25, y=display.actualContentHeight*.4} )
polygonB:tap( {x=display.actualContentWidth*.6, y=display.actualContentHeight*.3} )
polygonB:tap( {x=display.actualContentWidth*.7, y=display.actualContentHeight*.3} )
polygonB:tap( {x=display.actualContentWidth*.75, y=display.actualContentHeight*.4} )
polygonB:tap( {x=display.actualContentWidth*.75, y=display.actualContentHeight*.5} )
polygonB:tap( {x=display.actualContentWidth*.7, y=display.actualContentHeight*.6} )
polygonB:tap( {x=display.actualContentWidth*.6, y=display.actualContentHeight*.6} )
polygonB:tap( {x=display.actualContentWidth*.55, y=display.actualContentHeight*.5} )
polygonB:tap( {x=display.actualContentWidth*.55, y=display.actualContentHeight*.4} )
end
scene:addEventListener( "create", scene )
return scene
end
main.scenes.getPolygonIntersection = getPolygonIntersection
main.scenes.getPolygonIntersection()
--[[ closestLineAndPoint( points, pt ) ]]--
local function closestLineAndPoint()
local scene = composer.newScene("closestLineAndPoint")
local polygon = nil
local line = nil
local function update()
if (line and polygon) then
local found = math.closestLineAndPoint( line.b, polygon.spots )
for i=1, polygon.lines.numChildren or #polygon.lines do
local pt = found[1]
if (i == pt.index) then
polygon.lines[i].fill = {0,1,0}
else
polygon.lines[i].fill = {0,0,1}
end
line:update( pt.pt.x, pt.pt.y, line.b.x, line.b.y )
end
end
end
function scene:create( event )
local sceneGroup = self.view
-- Called when the scene is still off screen (but is about to come on screen).
local button = display.newText{ parent=sceneGroup, x=20, y=20, text="math.closestLineAndPoint( pt, points )", fontSize=20 }
button.x = 20 + button.width/2
button.fill = {.5,.75,1}
polygon = main.functions.newPolygon( sceneGroup, update )
polygon:tap( {x=display.actualContentWidth*.35, y=display.actualContentHeight*.35} )
polygon:tap( {x=display.actualContentWidth*.45, y=display.actualContentHeight*.15} )
polygon:tap( {x=display.actualContentWidth*.55, y=display.actualContentHeight*.35} )
polygon:tap( {x=display.actualContentWidth*.75, y=display.actualContentHeight*.45} )
polygon:tap( {x=display.actualContentWidth*.55, y=display.actualContentHeight*.55} )
polygon:tap( {x=display.actualContentWidth*.45, y=display.actualContentHeight*.75} )
polygon:tap( {x=display.actualContentWidth*.35, y=display.actualContentHeight*.55} )
polygon:tap( {x=display.actualContentWidth*.15, y=display.actualContentHeight*.45} )
line = main.functions.newLine( sceneGroup, 100, 100, 400, 100, update, nil, nil, 15, 30, {1,1,1}, {0,1,0} )
line.a:stopTouch()
update()
end
scene:addEventListener( "create", scene )
return scene
end
main.scenes.closestLineAndPoint = closestLineAndPoint
main.scenes.closestLineAndPoint()
--[[ closestPolygonIntersection( a, b, polygon ) ]]--
local function closestPolygonIntersection()
local scene = composer.newScene("closestPolygonIntersection")
local polygon = nil
local line = nil
local points = {}
local function update()
if (polygon and line) then
local found = math.polygonLineIntersection( polygon.spots, line.a, line.b, true )
for i=1, #points do
local point = points[i]
point.isVisible = (i <= #found)
if (point.isVisible) then
point.x, point.y = found[i].x, found[i].y
end
end
end
end
function scene:create( event )
local sceneGroup = self.view
-- Called when the scene is still off screen (but is about to come on screen).
local button = display.newText{ parent=sceneGroup, x=20, y=20, text="math.closestPolygonIntersection( a, b, polygon )", fontSize=20 }
button.x = 20 + button.width/2
button.fill = {.5,.75,1}
polygon = main.functions.newPolygon( sceneGroup, update )
polygon:tap( {x=display.actualContentWidth*.35, y=display.actualContentHeight*.35} )
polygon:tap( {x=display.actualContentWidth*.45, y=display.actualContentHeight*.15} )
polygon:tap( {x=display.actualContentWidth*.55, y=display.actualContentHeight*.35} )
polygon:tap( {x=display.actualContentWidth*.75, y=display.actualContentHeight*.45} )
polygon:tap( {x=display.actualContentWidth*.55, y=display.actualContentHeight*.55} )
polygon:tap( {x=display.actualContentWidth*.45, y=display.actualContentHeight*.75} )
polygon:tap( {x=display.actualContentWidth*.35, y=display.actualContentHeight*.55} )
polygon:tap( {x=display.actualContentWidth*.15, y=display.actualContentHeight*.45} )
line = main.functions.newLine( sceneGroup, 100, 100, 400, 100, update, nil, nil, 30, 30, {0,1,0}, {0,0,1} )
for i=1, 10 do
local point = main.functions.newDragSpot( sceneGroup, 250, 200, {1,1,1}, 25-(2*i), nil, "" )
point.alpha = 1-(i/10)
points[ #points+1 ] = point
end
update()
end
scene:addEventListener( "create", scene )
return scene
end
main.scenes.closestPolygonIntersection = closestPolygonIntersection
main.scenes.closestPolygonIntersection()
--[[ math.bouncePointAgainstLine = function( line, pt ) ]]--
local function bouncePointAgainstLine()
local scene = composer.newScene("bouncePointAgainstLine")
local lineA, lineB = nil, nil
local reflect, bounce, inter, velocity = nil, nil, nil, nil
local function update( target )
local point = { x=lineB.b.x, y=lineB.b.y, velocity={ x=lineB.a.x-lineB.b.x, y=lineB.a.y-lineB.b.y } }
local success, a, b, c = math.bouncePointAgainstLine( lineA, point ) -- bounced point, reflected point, intersection
reflect.isVisible = not (c == nil)
bounce.isVisible = not (c == nil)
inter.isVisible = not (c == nil)
velocity.isVisible = not (c == nil)
if (c ~= nil) then
bounce.x, bounce.y = a.x, a.y
reflect.x, reflect.y = b.x, b.y
inter.x, inter.y = c.x, c.y
velocity.x, velocity.y = a.x+a.velocity.x, a.y+a.velocity.y
end
end
function scene:create( event )
local sceneGroup = self.view
-- Called when the scene is still off screen (but is about to come on screen).
local button = display.newText{ parent=sceneGroup, x=20, y=20, text="math.bouncePointAgainstLine( line, pt )", fontSize=20 }
button.x = 20 + button.width/2
button.fill = {.5,.75,1}
lineA = main.functions.newLine( sceneGroup, 100, 100, 400, 100, update )
lineB = main.functions.newLine( sceneGroup, 350, 200, 450, 300, update )
lineB.a.spot.fill = {0,1,0}
lineB.b.spot.fill = {0,0,1}
reflect = main.functions.newDragSpot( sceneGroup, 250, 100, {0,0,1}, 20, update, "a" )
bounce = main.functions.newDragSpot( sceneGroup, 200, 100, {0,1,0}, 20, update, "b" )
inter = main.functions.newDragSpot( sceneGroup, 200, 100, {1,0,0}, 20, update, "c" )
velocity = main.functions.newDragSpot( sceneGroup, 200, 100, {1,1,1}, 10, update, "d" )
update()
end
scene:addEventListener( "create", scene )
return scene
end
main.scenes.bouncePointAgainstLine = bouncePointAgainstLine
main.scenes.bouncePointAgainstLine()
--[[ bouncePointAgainstPolygon( points, pt ) ]]--
local function bouncePointAgainstPolygon()
local scene = composer.newScene("bouncePointAgainstPolygon")
local polygon = nil
local line = nil
local bounce = nil
local point = nil
local function update()
if (line and bounce and polygon) then
local velocity = { x=line.b.x-line.a.x, y=line.b.y-line.a.y }
local pt = { x=line.a.x, y=line.a.y, velocity=velocity }
local reflect, found = math.bouncePointAgainstPolygon( polygon.spots, pt )
point.isVisible = (#found > 0)
if (#found > 0) then
while (bounce.numChildren > 0) do
bounce[1]:removeSelf()
end
for i=1, #found do
display.newCircle( bounce, found[1].x, found[1].y, 5 )
end
local l = nil
if (#found == 1) then
l = display.newLine( bounce, found[1].x, found[1].y, reflect.x+reflect.velocity.x, reflect.y+reflect.velocity.y )
elseif (#found > 1) then
l = display.newLine( bounce, found[1].x, found[1].y, found[2].x, found[2].y )
for i=3, #found do
l:append( found[i].x, found[i].y )
end
l:append( reflect.x+reflect.velocity.x, reflect.y+reflect.velocity.y )
end
l.strokeWidth = 3
point.x, point.y = reflect.x+reflect.velocity.x, reflect.y+reflect.velocity.y
-- bounce:update( found[1].x, found[1].y, reflect.x, reflect.y )
end
end
end
function scene:create( event )
local sceneGroup = self.view
-- Called when the scene is still off screen (but is about to come on screen).
local button = display.newText{ parent=sceneGroup, x=20, y=20, text="math.bouncePointAgainstPolygon( pt, points )", fontSize=20 }
button.x = 20 + button.width/2
button.fill = {.5,.75,1}
polygon = main.functions.newPolygon( sceneGroup, update )
polygon:tap( {x=display.actualContentWidth*.30, y=display.actualContentHeight*.35} )
polygon:tap( {x=display.actualContentWidth*.70, y=display.actualContentHeight*.35} )
polygon:tap( {x=display.actualContentWidth*.70, y=display.actualContentHeight*.70} )
polygon:tap( {x=display.actualContentWidth*.30, y=display.actualContentHeight*.70} )
polygon:tap( {x=display.actualContentWidth*.05, y=display.actualContentHeight*.4} )
polygon:tap( {x=display.actualContentWidth*.27, y=display.actualContentHeight*.6} )
line = main.functions.newLine( sceneGroup, 100, 100, 400, 400, update, nil, nil, 30, 30, {0,1,0}, {0,0,1} )
bounce = display.newGroup()
sceneGroup:insert( bounce )
-- bounce = main.functions.newLine( sceneGroup, 100, 150, 400, 150, nil, nil, nil, 15, 15, {1,1,1}, {0,1,0} )
-- bounce.a:stopTouch()
-- bounce.b:stopTouch()
point = main.functions.newDragSpot( sceneGroup, 250, 200, {0,1,1}, 15, nil, "B" )
update()
end
scene:addEventListener( "create", scene )
return scene
end
main.scenes.bouncePointAgainstPolygon = bouncePointAgainstPolygon
main.scenes.bouncePointAgainstPolygon()
--[[ Go to main menu ]]--
back:touch{ phase="ended" }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment