Last active
March 11, 2018 18:56
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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