Skip to content

Instantly share code, notes, and snippets.

@HoraceBury
Last active March 11, 2018 18:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save HoraceBury/3432b0c58b04fc7df8b3d810dc2caa13 to your computer and use it in GitHub Desktop.
Save HoraceBury/3432b0c58b04fc7df8b3d810dc2caa13 to your computer and use it in GitHub Desktop.
Cycling ants. Takes a pattern to be used for a mask outline and generates a collection of display groups which can be used to animate the cyclic pattern around a path shape. https://youtu.be/4cwjTrpvnFU
require("mathlib")
require("graphicslib")
require("tablelib")
local function asPoint( x, y )
return { x=x, y=y }
end
local function getPoint( path, index )
return { x=path[index], y=path[index+1] }
end
--[[
Returns a path of given length, extracted from a parent path, using optional starting offsets.
Parameters:
path: path to extract segment from
len: length of path
offset: position to start from (optional)
offset.index: index in path to begin at
offset.x, offset.y: actual position to measure from (used in preference to offset.len)
offset.len: distance from path[ offset.index ] to take segment from (default: 0)
Returns:
segment: {x,y,...} path of points extracted from provided path - nil if no result found
offset: position of the end of the segment in provided path
offset.index: index to begin from on next use
offset.x, offset.y: exact position to begin extraction
]]--
local function getPathSegment( path, len, offset )
if (path == nil or len == nil or len <= 0) then
return nil
end
offset = offset or {}
local index = offset.index or 1
local offsetlen = offset.len or 0
local x, y = offset.x, offset.y
if (x == nil or y == nil) then
x, y = math.extrudeToLen( getPoint( path, index ), getPoint( path, index+2 ), offsetlen )
end
local segment = { x, y }
while (len > 0) do
local sublen = 0
if (index < #path-1) then
sublen = math.lengthOf( x, y, path[ index+2 ], path[ index+3 ] )
else
segment[ #segment+1 ] = path[ index+2 ]
segment[ #segment+1 ] = path[ index+3 ]
index = #path-1
break
end
if (sublen <= len) then
len = len - sublen
index = index + 2
x, y = path[ index ], path[ index+1 ]
if (sublen > 0.01) then
segment[ #segment+1 ] = x
segment[ #segment+1 ] = y
end
else -- if (sublen > len) then
x, y = math.extrudeToLen( asPoint(x,y), getPoint( path, index+2 ), len )
len = 0
segment[ #segment+1 ] = x
segment[ #segment+1 ] = y
end
end
return segment, { index=index, x=x, y=y }
end
local function scalePattern( path, pattern )
local pathlen = math.lengthOf( unpack( path ) )
local patternlen = math.sum( unpack( pattern ) )
local division = pathlen / patternlen
local floored = math.floor( division )
local scale = division / floored
for i=1, #pattern do
pattern[i] = pattern[i] * scale
end
pattern.scale = scale
return pattern
end
local function getPatterns( _pattern )
local pattern = table.copy( _pattern )
pattern.flag = 1
pattern.scale = _pattern.scale
local function generatePatterns( pattern )
local p = table.copy( pattern, { 0 } )
p.flag = pattern.flag
p.scale = pattern.scale
local list = {}
for i=1*pattern.scale, pattern[1], pattern.scale do
local item = table.copy( p )
item.flag = p.flag
item[1] = item[1] - i
item[#item] = item[#item] + i
list[ #list+1 ] = item
end
table.remove( list[#list], 1 )
list[ #list ].flag = 1 - list[ #list ].flag
return list
end
local function generateAllPatterns( pattern )
local list = {}
for i=1, #pattern do
list = table.copy( list, generatePatterns( pattern ) )
table.insert( pattern, table.remove( pattern, 1 ) )
pattern.flag = 1 - pattern.flag
end
return list
end
return generateAllPatterns( pattern )
end
local function renderPattern( path, pattern )
local group = display.newGroup()
local patternindex = 1
local visible, segment, offset = true
while (segment == nil or #segment >= 4) do
segment, offset = getPathSegment( path, pattern[ patternindex ], offset )
if (#segment < 4) then
break
end
local line = display.newLine( group, unpack( segment ) )
line.stroke = {1,1,1}
line.strokeWidth = 5
if (pattern.flag == nil) then
line.isVisible = visible
visible = not visible
else
line.isVisible = (patternindex % 2 == pattern.flag)
end
patternindex = patternindex + 1
if (patternindex > #pattern) then
patternindex = 1
end
end
return group
end
local function measureDistanceAlongPath( path, len )
local index = 1
local sublen = math.lengthOf( unpack( table.range( path, index, 4 ) ) )
while (len > 0.01 and sublen < len) do
len = len - sublen
index = index + 2
if (index > #path) then
index = 1
end
if (index == #path-1) then
sublen = math.lengthOf( path[index], path[index+1], path[1], path[2] )
else
sublen = math.lengthOf( unpack( table.range( path, index, 4 ) ) )
end
end
local a, b = {x=path[index],y=path[index+1]}, {x=path[index+2],y=path[index+3]}
if (index == #path-1) then
b = {x=path[1],y=path[2]}
end
local x, y = math.extrudeToLen( a, b, len )
return index, len, x, y
end
local function shiftEndPoints( path, len )
local index, l, x, y = measureDistanceAlongPath( path, len )
if (l < 0.01) then
return table.copy(
table.range( path, index ),
table.range( path, 1, index+1 )
)
else
return table.copy(
{ x, y },
table.range( path, index+2 ),
table.range( path, 1, index+1 ),
{ x, y }
)
end
end
local function renderCyclicPatterns( parent, path, pattern )
local len = math.sum( unpack( pattern ) )
for i=1, len do
local tmppath = shiftEndPoints( path, i )
local group = renderPattern( tmppath, pattern )
parent:insert( group )
end
end
local function cyclingPts()
local x, y = 50, 50
local _, left = graphics.newSemiCircle( -x, 0, y, 20, 180 )
local _, right = graphics.newSemiCircle( x, 0, y, 20, 0 )
local path = table.copy( left, right, { left[1], left[2] } )
local pattern = { 10, 2, 5, 2 } -- on, off
pattern = scalePattern( path, pattern )
local group = display.newGroup()
group.x, group.y = 200, 200
renderCyclicPatterns( group, path, pattern )
local showindex, direction = 1, 1
local function iterate()
for i=1, group.numChildren do
group[ i ].isVisible = (i == showindex)
end
showindex = showindex + direction
if (showindex > group.numChildren) then
showindex = 1
elseif (showindex < 1) then
showindex = group.numChildren
end
end
local timed = timer.performWithDelay( 1, iterate, 0 )
local ispaused = false
local function tap(e)
if (e.numTaps == 2) then
direction = direction * -1
else
if (ispaused) then
timer.resume(timed)
else
timer.pause(timed)
end
ispaused = not ispaused
end
end
Runtime:addEventListener( "tap", tap )
end
cyclingPts()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment