Created
February 6, 2021 22:25
-
-
Save tnlogy/3700c8dc44409222153ad17309b628e2 to your computer and use it in GitHub Desktop.
Lua code for Codea to create a physics polygon for a sprite
This file contains 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
--# Main | |
function setup() | |
viewer.mode = FULLSCREEN | |
walls = { | |
physics.body(EDGE, vec2(0, 0), vec2(WIDTH, 0)), | |
physics.body(EDGE, vec2(0, 0), vec2(0, HEIGHT)), | |
physics.body(EDGE, vec2(WIDTH, HEIGHT), vec2(WIDTH, 0)) | |
} | |
for k, v in ipairs(walls) do | |
v.restitution = .5 | |
end | |
bodies = {} | |
add() | |
end | |
function add() | |
local assets = asset.builtin.Tyrian_Remastered.all | |
local r = math.random(1, #assets) | |
print(r) | |
img = readImage(assets[r]) | |
pts = simplify(outline(img), 1) | |
print("simplified to ", #pts) | |
for i = 1,#pts do | |
pts[i] = pts[i] - vec2(img.width/2,img.height/2) | |
end | |
obj=physics.body(POLYGON, unpack(pts)) | |
obj.x = math.random(100,WIDTH-100) | |
obj.y = HEIGHT | |
obj.angle = 180*math.random() | |
obj.restitution = .25 | |
obj.info = img | |
table.insert(bodies, obj) | |
end | |
function draw() | |
background() | |
strokeWidth(1) | |
translate(WIDTH/2,HEIGHT/2) | |
local pp = nil | |
for i,v in ipairs(pts) do | |
if pp then | |
line(pp.x,pp.y,v.x,v.y) | |
end | |
pp = v | |
end | |
for i,obj in ipairs(bodies) do | |
resetMatrix() | |
translate(obj.x,obj.y) | |
rotate(obj.angle) | |
spriteMode(CENTER) | |
sprite(obj.info,0,0) | |
end | |
end | |
function touched(touch) | |
if touch.state == BEGAN then | |
add() | |
end | |
end | |
--# RamerDouglasPeucker | |
-- Implementation of the Ramer–Douglas–Peucker algorithm | |
-- @readme https://github.com/evaera/RobloxLuaAlgorithms#ramerdouglaspeuckerlua | |
-- @author evaera | |
-- and now modified for Codea | |
function getSqSegDist(p, p1, p2) | |
local x = p1.x | |
local y = p1.y | |
local dx = p2.x - x | |
local dy = p2.y - y | |
if dx ~= 0 or dy ~= 0 then | |
local t = ((p.x - x) * dx + (p.y - y) * dy) / (dx * dx + dy * dy) | |
if t > 1 then | |
x = p2.x | |
y = p2.y | |
elseif t > 0 then | |
x = x + dx * t | |
y = y + dy * t | |
end | |
end | |
dx = p.x - x | |
dy = p.y - y | |
return dx * dx + dy * dy | |
end | |
function simplifyRadialDist(points, sqTolerance) | |
local prevPoint = points[1] | |
local newPoints = {prevPoint} | |
local point | |
for i=2, #points do | |
point = points[i] | |
if point:distSqr(prevPoint) > sqTolerance then | |
table.insert(newPoints, point) | |
prevPoint = point | |
end | |
end | |
if prevPoint ~= point then | |
table.insert(newPoints, point) | |
end | |
return newPoints | |
end | |
function simplifyDPStep(points, first, last, sqTolerance, simplified) | |
local maxSqDist = sqTolerance | |
local index | |
for i=first+1, last do | |
local sqDist = getSqSegDist(points[i], points[first], points[last]) | |
if sqDist > maxSqDist then | |
index = i | |
maxSqDist = sqDist | |
end | |
end | |
if maxSqDist > sqTolerance then | |
if index - first > 1 then | |
simplifyDPStep(points, first, index, sqTolerance, simplified) | |
end | |
table.insert(simplified, points[index]) | |
if last - index > 1 then | |
simplifyDPStep(points, index, last, sqTolerance, simplified) | |
end | |
end | |
end | |
function simplifyDouglasPeucker(points, sqTolerance) | |
local last = #points | |
local simplified={points[1]} | |
simplifyDPStep(points, 1, last, sqTolerance, simplified) | |
table.insert(simplified, points[last]) | |
return simplified | |
end | |
function simplify(points, tolerance, highestQuality) | |
if #points <= 2 then | |
return points | |
end | |
local sqTolerance = tolerance ~= nil and tolerance^2 or 1 | |
points = highestQuality and points or simplifyRadialDist(points, sqTolerance) | |
points = simplifyDouglasPeucker(points, sqTolerance) | |
return points | |
end | |
--# Outline | |
-- Find the outline of a sprite | |
-- by walking around it, like finding | |
-- your way in a labyrinth by following | |
-- the wall on the right side | |
function outline(img) | |
local w,h = img.width, img.height | |
local dir, right = vec2(1,0), vec2(0,1) | |
function turn(d) | |
right = d == right and -dir or dir | |
dir = d | |
end | |
function empty(img,p) | |
if 0 < p.x and p.x < w and p.y > 0 and p.y < h then | |
r,g,b,a = img:get(p.x,p.y) | |
return a == 0 | |
end | |
return true | |
end | |
local sp = false -- start point | |
local pts = {} | |
local pos = vec2(-1,math.ceil(h/2)) | |
repeat | |
local p = pos + dir | |
local pr = pos + right | |
local a = empty(img, p) | |
local ar = empty(img, pr) | |
if not sp then | |
-- find start pos | |
if a then | |
pos = p | |
elseif pos.x > w then | |
-- missed target | |
return {} | |
else -- hit wall | |
sp = pos | |
table.insert(pts,pos) | |
turn(-right) | |
end | |
elseif ar then | |
turn(right) | |
pos = pos + dir | |
table.insert(pts,pos) | |
elseif a then | |
pos = p | |
table.insert(pts,p) | |
else -- wall ahead | |
turn(-right) | |
end | |
until pos == sp and #pts > 1 | |
print(#pts, " points") | |
return pts | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment