--# Main
--Main
supportedOrientations(LANDSCAPE_ANY)
resolution=5
-- Use this function to perform your initial setup
function setup()
lineCapMode(ROUND)
debugDraw = PhysicsDebugDraw()
defaultGravity = physics.gravity()
createContainer()
parameter.boolean("Show_Outline")
print("Use the 'Show outline' setting above to show/hide the graphics object behind the image")
img = readImage("Tyrian Remastered:Blimp Boss")
mySprite=Object(img,resolution,300-math.random(150),HEIGHT+50-math.random(75),math.random(360))
mySprite2=Object(img,resolution,600-math.random(150),HEIGHT-50-math.random(75),math.random(360))
end
--create container around sides and floor
function createContainer()
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
end
function cleanup()
clearOutput()
debugDraw:clear()
end
-- This function gets called once every frame
function draw()
background(201, 213, 217, 183)
physics.gravity(defaultGravity)
debugDraw:draw()
end
--# Object
Object = class()
local imge
function Object:init(img,res,xx,yy,angle)
imge=img -- ?? I wanted to use self.image but the draw routine didn't recognise it
--next two lines calculate x,y offset from start of physics object
offsetX=img.width/2+1 --add 1 because vector goes round the outside of the image
offsetY=img.height/2+1
points = VectorOutline:CreatePhysics(img,res,true) --create physics object from image
--the rest of these lines add a new physics object
poly=physics.body(POLYGON, unpack(points))
poly.x = xx
poly.y = yy
poly.angle=angle
poly.sleepingAllowed = false
poly.restitution = 0.25
debugDraw:addBody(poly)
end
function Object:draw(xx,yy,angle)
pushMatrix() -- Preserve the current matrix
pushStyle() -- Preserve the current style
resetMatrix() -- Reset the matrix: 'origin' in the bottom left corner
resetStyle() -- Reset the style
translate(xx, yy) -- Shift (translate) origin of Viewer by (x, y)
rotate(angle) -- Rotate the Viewer about that origin
spriteMode(CENTER)
sprite(imge, offsetX,offsetY) -- Draw at the (shifted, rotated) origin
popStyle() -- Restore the style to what it was
popMatrix() -- Restore the matrix to what it was
end
--# PhysicsDebugDraw
--# PhysicsDebugDraw
PhysicsDebugDraw = class()
function PhysicsDebugDraw:init()
self.bodies = {}
end
function PhysicsDebugDraw:addBody(body)
table.insert(self.bodies,body)
end
function PhysicsDebugDraw:clear()
for i,body in ipairs(self.bodies) do
body:destroy()
end
self.bodies = {}
end
function PhysicsDebugDraw:draw()
for i,body in ipairs(self.bodies) do
pushMatrix()
translate(body.x, body.y)
rotate(body.angle)
if body.type == DYNAMIC then
Object:draw(body.x,body.y,body.angle)
if Show_Outline then
local points = body.points
pushStyle()
strokeWidth(3.0)
for j = 1,#points do
a = points[j]
b = points[(j % #points)+1]
line(a.x, a.y, b.x, b.y)
end
popStyle()
end
end
popMatrix()
end
end
function PhysicsDebugDraw:collide(contact)
if contact.state == BEGAN then
self.contacts[contact.id] = contact
sound(SOUND_HIT, 2643)
elseif contact.state == MOVING then
self.contacts[contact.id] = contact
elseif contact.state == ENDED then
self.contacts[contact.id] = nil
end
end
--# VectorOutline
VectorOutline = class()
--This code creates a vector outline of any image so you can use it as a physics object
--Created by Dermot Balson (user Ignatz)
--Version 1.00 Feb 2013
--USAGE
-- To have the code return a set of vectors which you can copy into your own code
-- points=VectorOutline:CreatePhysics(img,res)
-- poly=physics.body(POLYGON, unpack(points)) --create the actual object
--To create a vector outline of our image, we need to go through several steps
--1. We need to identify the outline all the way round
--2. We need to mark the outline as an array of pixels, working anti clockwise (as the physics code requires)
--3. We clean it up, removing any unnecessary pixels in the middle of straight lines (we only need the ends)
--4. We walk around our array of pixels, taking every Nth pixel
-- (N is specified by the user to balance speed and accuracy)
--The result is a physics object, or, if you prefer, a set of vectors that you can turn into one yourself
--Disclaimer - at time of writing, I have just one week's experience in Codea. Improvements are welcome!
OUTERBLANK=1 --cells which are blank and outside the image, ie not enclosed by filled cells
FILLED=2 --marks cells in bitmap which are used for the picture
UNUSEDEDGE=3 --marks blank/transparent cells in the bitmap which border on filled cells
USEDEDGE=4 --marks edge cells which have been used for creating the vector object, to avoid re-use
EMPTY=0 --marks blank cells which are none of the above
rows,cols=0,0
function VectorOutline:CreatePhysics(img,res)
cols=img.width
rows=img.height
--key array to store map of image that we will work with
--has 2 extra rows/cols around the edge so we can fit our border around it
c=array2D(cols+4,rows+4)
firstBlankCol,firstBlankRow = 0,0 --to store location of first blank cell discovered
--first set values for nonblank cells
for i=1,cols do
for j=1,rows do
r,g,b,a=img:get(i,j)
if a>0 then
c[i+2][j+2]=FILLED --indent two rows and cols to allow for border
if firstBlankCol==0 then firstBlankCol=i+2 firstBlankRow=j+2 end
end
end
end
--now fill in outside of image, ie blank cells that are not enclosed by filled cells
findEdge(c,firstBlankCol,firstBlankRow)
--make path of edge cells, working anticlockwise
path=FindEdgePath(c)
--trim path to remove points within straight lines
path=trimStraightLines(path)
--create final path of vectors
points=createPath(path,res)
return points
end
--this function works recursively to find all blank cells which are not embedded in the image. It does this
--by starting with a blank cell outside the image, and looking at all neighbours. Any blank neighbours are
--examined in turn.
--Blank cells outside the image are labelled OUTERBLANK if they are not adjacent to a filled cell, or
--UNUSEDEDGE if they are adjacent to a filled cell. These latter cells form the border around our image.
function findEdge(c,i,j)
c[i][j]=OUTERBLANK
local filledNeighbor=false
for ii=-1,1 do
for jj=-1,1 do
iii=i+ii
jjj=j+jj
if iii>0 and iii<=cols+4 and jjj>0 and jjj<=rows+4 then
if c[iii][jjj]==EMPTY then
findEdge(c,iii,jjj)
elseif c[iii][jjj]==FILLED then
filledNeighbor=true
end
end
end
end
if filledNeighbor==true then c[i][j]=UNUSEDEDGE end
end
--This function creates a path around the image using the chain of border cells identified above.
--It has to work anticlockwise, which it does by examining the neighbours of the last cell in
--the chain in a certain order, from lower left, anti clockwise round to the left. It loops
--round twice, first at a distance of one cell, then two cells
function FindEdgePath(c)
path={} --this will be the array of vectors
colA={ 0, 1, 0,-1, 0, 1, 0,-1} --these two rows specify the x,y offsets to look in
rowA={-1, 0, 1, 0,-2, 0, 2, 0} --around the current cell
i1=0
--find first edge cell
for i=1,cols+4 do
for j=1,rows+4 do
if c[i][j]==UNUSEDEDGE then i1,j1=i,j end
end
if i1>0 then break end
end
--add the first point
n=1
path[n]={i=i1,j=j1}
c[i1][j1]=USEDEDGE
--now work around the image anticlockwise
i,j=i1,j1
finished=false
while finished==false do
for u=1,#colA do
iii,jjj=i,j
ii=i+colA[u]
jj=j+rowA[u]
if ii>0 and ii<=cols+4 and jj>0 and jj<=rows+4 then
if c[ii][jj]==UNUSEDEDGE then
n = n + 1
if n>1000 then
print("ERROR - Infinite loop in creating outline")
finished=true
end
path[n]={i=ii,j=jj}
c[ii][jj]=USEDEDGE
iii,jjj=ii,jj
break
elseif ii==i1 and jj==j1 and n> 5 then --are we done?
finished=true
break
end
end
end
if iii==i and jjj==j then
--print("ERROR - Unable to complete outline")
finished=true
else
i,j=iii,jjj
end
end
return path
end
--we only need to store the start and end cells for straight lines
--This function removes cells in between
function trimStraightLines(path)
local i0,j0=0,0
local ni,nj=0,0
for p=#path,1,-1 do
i,j=path[p].i,path[p].j
if i==i0 then
ni = ni + 1
else
if ni>2 then
for u=p+ni-2,p+1,-1 do
table.remove(path,p)
end
end
ni=1
i0=i
end
if j==j0 then
nj = nj + 1
else
if nj>2 then
for u=p+nj-2,p+1,-1 do
table.remove(path,p)
end
end
nj=1
j0=j
end
end
return path
end
--This function creates the final vector array
--it loops through the array, selecting points at least N pixels apart, where N is specified by the user
--to balance accuracy and speed
function createPath(path,N)
NN=N*N --see below for reason for this
local points={}
table.insert(points,vec2(path[1].i,path[1].j))
i0,j0=path[1].i,path[1].j
for p=2,#path-1 do
--skip points until we are at least r from the previous point
--the calculation of distance between two points is the SQRT of the sum of square of the x and y diffs,
--but to save doing the SQRT, we compare it with r squared instead
if (path[p].i-i0)^2+(path[p].j-j0)^2>NN then
table.insert(points,vec2(path[p].i,path[p].j))
i0,j0=path[p].i,path[p].j
end
end
return points
end
function drawImage(img,c)
local img2=image(cols+4,rows+4)
for i=1,cols do
for j=1,rows do
r,g,b,a=img:get(i,j)
if a>0 then img2:set(i+2,j+2,color(r,g,b,a)) end
end
end
return img2
end
function drawTestImage(c) --testing only
local img2=image(cols+4,rows+4)
for i=1,cols+4 do
for j=1,rows+4 do
if c[i][j]==UNUSEDEDGE then
img2:set(i,j,color(255,255,255,255))
elseif c[i][j]==OUTERBLANK then
img2:set(i,j,color(0,0,255,50))
elseif c[i][j]==FILLED then
img2:set(i,j,color(255,0,0,100))
elseif c[i][j]==USEDEDGE then
img2:set(i,j,color(0,0,255,255))
elseif c[i][j]==FLAG then
img2:set(i,j,color(255,0,0,255))
img2:set(i-1,j,color(255,0,0,255))
img2:set(i+1,j,color(255,0,0,255))
img2:set(i,j-1,color(255,0,0,255))
img2:set(i,j+1,color(255,0,0,255))
end
end
end
for p=1,#path do
i,j=path[p].i,path[p].j
img2:set(i,j,color(255,255,0,255))
end
return img2
end
function array1D(cols,defaultValue)
if defaultValue==nil then defaultValue=0 end
local A={}
for i=1,cols do
A[i]=defaultValue
end
end
function array2D(rows,cols,defaultValue,start)
if defaultValue==nil then defaultValue=0 end
if start==nil then start=1 end
local A={}
for i=start,rows do
A[i]={}
for j=start,cols do
A[i][j]=defaultValue
end
end
return A
end