Skip to content

Instantly share code, notes, and snippets.

@HoraceBury
Last active July 23, 2020 12:09
Show Gist options
  • Save HoraceBury/9544932 to your computer and use it in GitHub Desktop.
Save HoraceBury/9544932 to your computer and use it in GitHub Desktop.
Arrow projectile with air drag applied to feathered tail.
-- arrow test
-- http://www.iforce2d.net/b2dtut/sticky-projectiles
-- video: http://screencast.com/t/vcPLSJR6xkIq
require("mathlib")
local sWidth, sHeight = display.actualContentWidth, display.actualContentHeight
local centerX, centerY = sWidth/2, sHeight/2
local arrows = display.newGroup()
-- original arrow image: http://content.screencast.com/users/HoraceBury/folders/Default/media/1d553487-b39f-440c-a89c-3d4e818cf20a/arrow.png?downloadOnly=true
-- if you don't have/can't get this image, comment out this line...
local outline = graphics.newOutline( 4, "arrow.png" )
-- if you don't have the image, use this instead...
--outline = {157,0,223,13,153,24,0,12,156,0}
local launchPad = display.newCircle( 500, sHeight, 50 )
local maxDamping = 10
local multi = 10
local x, y = nil, nil
local zero = {x=0,y=0}
local one = {x=1,y=0}
local dragConstant = .25
local trim = 300
--[[ -- This was some working out while trying to directly convert (yet again) then "sticky projectiles" code into Lua... (didn't work)
local dc = .5 -- dragConstant
local pointingDirection = math.rotateTo( one, 45, zero )
local flightDirection = {x=12,y=0} -- arrow:getLinearVelocity()
local flightSpeed = math.normalise( flightDirection ) -- normalises flightDirection and returns the length
local dot = math.dotProduct( flightDirection, pointingDirection )
local mass = math.polygonArea( outline ) * 0.1 -- area * density
local dragForceMagnitude = (1 - math.abs(dot)) * flightSpeed * flightSpeed * dc * mass
print(mass)
print(dot)
print( math.dotProduct( -10,10 , 10,0 ) )
print(dragForceMagnitude) -- this is always wayyyyy too high
]]--
-- apply drag to tail of each arrow
Runtime:addEventListener( "enterFrame", function()
for i=1, arrows.numChildren do
-- get the arrow
local arrow = arrows[i]
-- get direction the arrow is facing
x, y = arrow:localToContent( 1, 0 )
local pointing = { x=x-arrow.x, y=y-arrow.y }
-- get direction of flight
x, y = arrow:getLinearVelocity()
local velocityVector = { x=x, y=y }
-- get the ballistic velocity
local velocity = math.lengthOf( x, y )
-- get drag direction
local dragDirection = { x=-x, y=-y }
-- get angle between the two
local angle = math.angleOf( {x=0,y=0}, pointing, velocityVector )
-- value of angle determines how much of the drag is applied (0 - 180)
local fraction = math.fractionOf( 180, math.abs(angle) )
-- drag force
local dragForce = { x=dragConstant * dragDirection.x, y=dragConstant * dragDirection.y }
-- feathers location
x, y = arrow:localToContent( -100, 0 )
local feathers = { x=x, y=y }
-- apply drag if it is above a certain amount (an arrow dropping to the ground should just flop down after nose diving)
if (velocity > trim) then
-- choose the version which you prefer...
-- drag is relative to the angle between the direction of travel and facing direction
--arrow:applyForce( fraction*dragForce.x, fraction*dragForce.y, feathers.x, feathers.y )
-- drag is relative to speed
arrow:applyForce( dragForce.x, dragForce.y, feathers.x, feathers.y )
end
end
end )
-- tap to fire arrow - location and distance from launchPad will indicate direction and speed of flight
Runtime:addEventListener( "tap", function(e)
local angle = math.angleOf( launchPad, e )
local arrow = display.newImage( arrows, "arrow.png" )
arrow.x, arrow.y = e.x, e.y
arrow.rotation = angle
physics.addBody( arrow, "dynamic", { outline=outline, friction=1, density=1, bounce=.1, } )
arrow.angularDamping = 1
arrow:setLinearVelocity( (e.x-launchPad.x)*multi, (e.y-sHeight)*multi )
end )
settings = {
orientation =
{
default = "landscapeLeft",
},
iphone =
{
plist=
{
UIStatusBarHidden=true,
CFBundleIconFile = "Icon.png",
CFBundleIconFiles = {
"Icon.png",
"Icon@2x.png",
"Icon-72.png",
},
},
}
}
application =
{
content =
{
width = 1024*3,
height = 768*3,
scale = "letterbox",
xAlign = "left",
yAlign = "top",
antialias = true,
imageSuffix =
{
["-x2"] = 2,
["-x4"] = 4,
},
}
}
-- arrow test
-- http://www.iforce2d.net/b2dtut/sticky-projectiles
display.setStatusBar( display.HiddenStatusBar )
require("physics")
physics.start()
physics.setDrawMode("hybrid")
physics.setGravity( 0, 9.8 )
local sWidth, sHeight = display.actualContentWidth, display.actualContentHeight
local centerX, centerY = sWidth/2, sHeight/2
physics.addBody( display.newRect( centerX, 0, sWidth, 20 ), "static" )
physics.addBody( display.newRect( centerX, sHeight, sWidth, 20 ), "static" )
physics.addBody( display.newRect( 0, centerY, 20, sHeight ), "static" )
physics.addBody( display.newRect( sWidth, centerY, 20, sHeight ), "static" )
require("arrowlib")
-- mathlib.lua
--[[
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/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
]]--
--[[
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:
]]--
-- rounds up to the nearest multiple of the number
local function nearest( number, multiple )
return math.round( (number / multiple) ) * multiple
end
math.nearest = nearest
-- 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
-- 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
--[[ Support values for angles ]]--
local PI = (4*math.atan(1))
local quickPI = 180 / PI
math.PI, math.quickPI = PI, quickPI
--[[
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 = arg[1], arg[2], arg[3]
if (#arg == 1) then
-- angle of a relative to (0,0)
return math.atan2( a.y, a.x ) * 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
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.angleBetweenPoints( target, target.prevevent )
local b = mathlib.angleBetweenPoints( 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
-- returns the distance between points a and b
-- b is optional. assumes distance from 0,0 if b is nil
local function lengthOf( a, b )
if (type(a) == "number") then
a = {x=a,y=b}
b = nil
end
if (b == nil) then
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
math.lengthOf = lengthOf
--[[
Description:
Extends the point away from or towards the origin to the length of len.
Params:
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
Returns:
{x,y} = extruded point
]]--
local function extrudeToLen( origin, point, lenOrMin, max )
local length = 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
return x + origin.x, y + origin.y, x, y
end
math.extrudeToLen = extrudeToLen
--[[
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
-- calculates the area of a polygon
-- will not calculate area for self-intersecting polygons (where vertices cross each other)
-- points: table of {x,y} points
-- 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
-- Returns true if the dot { x,y } is within the polygon defined by points table { {x,y},{x,y},{x,y},... }
local function pointInPolygon( points, dot )
local i, j = #points, #points
local oddNodes = false
for i=1, #points 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.pointInPolygon = pointInPolygon
-- Return true if the dot { x,y } is within any of the polygons in the list
local function isPointInPolygons( polygons, dot )
for i=1, #polygons do
if (pointInPolygon( polygons[i], dot )) then
return true
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 = isPolyClockwise
-- returns true if the middle point is concave when viewed as part of a polygon
-- a, b, c are {x,y} points
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
-- assumes points are {x,y} tables
-- returns nil if there are not enough points ( < 3 )
-- can accept a display group
local function isPolygonConcave( points )
local count = points.numChildren
if (count == nil) then
count = #points
end
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
-- assumes polygon is standard display format: { x,y,x,y,x,y,x,y, ... }
-- returns collection of intersection points with the polygon line's index {x,y,lineIndex}
-- sort: true to sort the points into order from a to b
local function polygonLineIntersection( polygon, a, b, sort )
local points = {}
for i=1, #polygon-3, 2 do
local success, pt = math.doLinesIntersect( a, b, { x=polygon[i], y=polygon[i+1] }, { x=polygon[i+2], y=polygon[i+3] } )
if (success) then
pt.lineIndex = i
points[ #points+1 ] = pt
end
end
if (sort) then
table.sort( points, function(a,b) return math.lengthOf(e,a) > math.lengthOf(e,b) end )
end
return points
end
math.polygonLineIntersection = polygonLineIntersection
--[[
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
--[[
Polygons
]]--
--[[
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 midPoint( ... )
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=x/c, y=y/c }
end
math.midPoint = midPoint
--[[
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
-- 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 from north to south
local function reflect( north, south, point )
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.reflect = reflect
-- 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 true, {x=x, y=y}
end
return false
end
math.doLinesIntersect = doLinesIntersect
-- 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 or true) 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
-- 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment