Advertisement
Dermotb

Vector Outline of image

Feb 25th, 2013
67
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 15.50 KB | None | 0 0
  1.  
  2. --# Main
  3. --Main
  4. supportedOrientations(LANDSCAPE_ANY)
  5.  
  6. resolution=5
  7.  
  8. -- Use this function to perform your initial setup
  9. function setup()
  10. lineCapMode(ROUND)
  11. debugDraw = PhysicsDebugDraw()
  12. defaultGravity = physics.gravity()
  13. createContainer()
  14.  
  15. img = readImage("Tyrian Remastered:Blimp Boss")
  16. mySprite=Object(img,resolution,300-math.random(150),HEIGHT+50-math.random(75),math.random(360))
  17. mySprite2=Object(img,resolution,600-math.random(150),HEIGHT-50-math.random(75),math.random(360))
  18. end
  19.  
  20. --create container around sides and floor
  21. function createContainer()
  22. walls = {
  23. physics.body(EDGE, vec2(0, 0), vec2(WIDTH, 0)),
  24. physics.body(EDGE, vec2(0, 0), vec2(0, HEIGHT)),
  25. physics.body(EDGE, vec2(WIDTH, HEIGHT), vec2(WIDTH, 0)),
  26. --physics.body(EDGE, vec2(WIDTH, HEIGHT), vec2(0, HEIGHT)),
  27. }
  28. for k, v in ipairs(walls) do
  29. v.restitution = .5
  30. end
  31. end
  32.  
  33. function cleanup()
  34. clearOutput()
  35. debugDraw:clear()
  36. end
  37.  
  38. -- This function gets called once every frame
  39. function draw()
  40. -- This sets the background color
  41. background(201, 213, 217, 183)
  42. physics.gravity(defaultGravity)
  43. debugDraw:draw()
  44. end
  45.  
  46.  
  47. --# Object
  48. Object = class()
  49.  
  50. function Object:init(img,res,xx,yy,angle)
  51. self.x = xx
  52. self.y = yy
  53. self.angle =angle
  54. self.image=img
  55. imge=img -- ?? I wanted to use self.image but the draw routine didn't recognise it
  56. offsetX=img.width/2+1 --add 1 because vector goes round the outside of the image
  57. offsetY=img.height/2+1
  58. points = VectorOutline:CreatePhysics(img,res,true) --create physics object from image
  59. poly=physics.body(POLYGON, unpack(points))
  60. poly.x = xx
  61. poly.y = yy
  62. poly.angle=angle
  63. poly.sleepingAllowed = false
  64. poly.restitution = 0.25
  65. debugDraw:addBody(poly)
  66. end
  67.  
  68. function Object:draw(xx,yy,angle)
  69. pushMatrix() -- Preserve the current matrix
  70. pushStyle() -- Preserve the current style
  71. resetMatrix() -- Reset the matrix: 'origin' in the bottom left corner
  72. resetStyle() -- Reset the style
  73. xd,yd=RotateCoords(offsetY,offsetX,angle)
  74. x=xx+xd
  75. y=yy+yd
  76. translate(x, y) -- Shift (translate) origin of Viewer by (x, y)
  77. rotate(angle) -- Rotate the Viewer about that origin
  78. spriteMode(CENTER)
  79. sprite(imge, 0,0) -- Draw at the (shifted, rotated) origin
  80. popStyle() -- Restore the style to what it was
  81. popMatrix() -- Restore the matrix to what it was
  82. end
  83.  
  84. function RotateCoords(w,h,a)
  85. local c=(w*w+h*h)^.5
  86. local ang=math.asin(w/c)
  87. ang = ang + a/180*math.pi
  88. local aa=c*math.sin(ang)
  89. local bb=c*math.cos(ang)
  90. return bb,aa
  91. end
  92.  
  93.  
  94. --# PhysicsDebugDraw
  95. PhysicsDebugDraw = class()
  96.  
  97. xxxx=0
  98. yyyy=0
  99.  
  100. function PhysicsDebugDraw:init()
  101. self.bodies = {}
  102. self.joints = {}
  103. self.touchMap = {}
  104. self.contacts = {}
  105. end
  106.  
  107. function PhysicsDebugDraw:addBody(body)
  108. table.insert(self.bodies,body)
  109. end
  110.  
  111. function PhysicsDebugDraw:addJoint(joint)
  112. table.insert(self.joints,joint)
  113. end
  114.  
  115. function PhysicsDebugDraw:clear()
  116. -- deactivate all bodies
  117.  
  118. for i,body in ipairs(self.bodies) do
  119. body:destroy()
  120. end
  121.  
  122. for i,joint in ipairs(self.joints) do
  123. joint:destroy()
  124. end
  125.  
  126. self.bodies = {}
  127. self.joints = {}
  128. self.contacts = {}
  129. self.touchMap = {}
  130. end
  131.  
  132. function PhysicsDebugDraw:draw()
  133.  
  134. pushStyle()
  135. smooth()
  136. strokeWidth(5)
  137. stroke(128,0,128)
  138.  
  139. local gain = 2.0
  140. local damp = 0.5
  141. for k,v in pairs(self.touchMap) do
  142. local worldAnchor = v.body:getWorldPoint(v.anchor)
  143. local touchPoint = v.tp
  144. local diff = touchPoint - worldAnchor
  145. local vel = v.body:getLinearVelocityFromWorldPoint(worldAnchor)
  146. v.body:applyForce( (1/1) * diff * gain - vel * damp, worldAnchor)
  147.  
  148. line(touchPoint.x, touchPoint.y, worldAnchor.x, worldAnchor.y)
  149. end
  150.  
  151. stroke(0,255,0,255)
  152. strokeWidth(5)
  153. for k,joint in pairs(self.joints) do
  154. local a = joint.anchorA
  155. local b = joint.anchorB
  156. line(a.x,a.y,b.x,b.y)
  157. end
  158.  
  159. stroke(255,255,255,255)
  160. noFill()
  161.  
  162.  
  163. for i,body in ipairs(self.bodies) do
  164. pushMatrix()
  165. translate(body.x, body.y)
  166. rotate(body.angle)
  167.  
  168. if body.type == STATIC then
  169. stroke(255,255,255,255)
  170. elseif body.type == DYNAMIC then
  171. stroke(150,255,150,255)
  172. elseif body.type == KINEMATIC then
  173. stroke(150,150,255,255)
  174. end
  175.  
  176. if body.shapeType == POLYGON then
  177. strokeWidth(3.0)
  178. if body.x+body.y>0 then
  179. Object:draw(body.x,body.y,body.angle) --?? draw our bitmap
  180. else
  181. local points = body.points
  182. for j = 1,#points do
  183. a = points[j]
  184. b = points[(j % #points)+1]
  185. line(a.x, a.y, b.x, b.y)
  186. end
  187. end
  188. elseif body.shapeType == CHAIN or body.shapeType == EDGE then
  189. strokeWidth(3.0)
  190. local points = body.points
  191. for j = 1,#points-1 do
  192. a = points[j]
  193. b = points[j+1]
  194. line(a.x, a.y, b.x, b.y)
  195. end
  196. elseif body.shapeType == CIRCLE then
  197. strokeWidth(3.0)
  198. line(0,0,body.radius-3,0)
  199. ellipse(0,0,body.radius*2)
  200. end
  201.  
  202. popMatrix()
  203. end
  204.  
  205. stroke(255, 0, 0, 255)
  206. fill(255, 0, 0, 255)
  207.  
  208. for k,v in pairs(self.contacts) do
  209. for m,n in ipairs(v.points) do
  210. ellipse(n.x, n.y, 10, 10)
  211. end
  212. end
  213.  
  214. popStyle()
  215. end
  216.  
  217. function PhysicsDebugDraw:touched(touch)
  218. local touchPoint = vec2(touch.x, touch.y)
  219. if touch.state == BEGAN then
  220. for i,body in ipairs(self.bodies) do
  221. if body.type == DYNAMIC and body:testPoint(touchPoint) then
  222. self.touchMap[touch.id] = {tp = touchPoint, body = body, anchor = body:getLocalPoint(touchPoint)}
  223. return true
  224. end
  225. end
  226. elseif touch.state == MOVING and self.touchMap[touch.id] then
  227. self.touchMap[touch.id].tp = touchPoint
  228. return true
  229. elseif touch.state == ENDED and self.touchMap[touch.id] then
  230. self.touchMap[touch.id] = nil
  231. return true;
  232. end
  233. return false
  234. end
  235.  
  236. function PhysicsDebugDraw:collide(contact)
  237. if contact.state == BEGAN then
  238. self.contacts[contact.id] = contact
  239. sound(SOUND_HIT, 2643)
  240. elseif contact.state == MOVING then
  241. self.contacts[contact.id] = contact
  242. elseif contact.state == ENDED then
  243. self.contacts[contact.id] = nil
  244. end
  245. end
  246.  
  247. --# VectorOutline
  248. VectorOutline = class()
  249.  
  250. --This code creates a vector outline of any image so you can use it as a physics object
  251. --Created by Dermot Balson (user Ignatz)
  252. --Version 1.00 Feb 2013
  253.  
  254. --USAGE
  255. -- To have the code return a set of vectors which you can copy into your own code
  256. -- points=VectorOutline:CreatePhysics(img,res)
  257. -- poly=physics.body(POLYGON, unpack(points)) --create the actual object
  258.  
  259. --To create a vector outline of our image, we need to go through several steps
  260. --1. We need to identify the outline all the way round
  261. --2. We need to mark the outline as an array of pixels, working anti clockwise (as the physics code requires)
  262. --3. We clean it up, removing any unnecessary pixels in the middle of straight lines (we only need the ends)
  263. --4. We walk around our array of pixels, taking every Nth pixel
  264. -- (N is specified by the user to balance speed and accuracy)
  265. --The result is a physics object, or, if you prefer, a set of vectors that you can turn into one yourself
  266.  
  267. --Disclaimer - at time of writing, I have just one week's experience in Codea. Improvements are welcome!
  268.  
  269. OUTERBLANK=1 --cells which are blank and outside the image, ie not enclosed by filled cells
  270. FILLED=2 --marks cells in bitmap which are used for the picture
  271. UNUSEDEDGE=3 --marks blank/transparent cells in the bitmap which border on filled cells
  272. USEDEDGE=4 --marks edge cells which have been used for creating the vector object, to avoid re-use
  273. EMPTY=0 --marks blank cells which are none of the above
  274. rows,cols=0,0
  275.  
  276. function VectorOutline:CreatePhysics(img,res)
  277. cols=img.width
  278. rows=img.height
  279. --key array to store map of image that we will work with
  280. --has 2 extra rows/cols around the edge so we can fit our border around it
  281. c=array2D(cols+4,rows+4)
  282. firstBlankCol,firstBlankRow = 0,0 --to store location of first blank cell discovered
  283. --first set values for nonblank cells
  284. for i=1,cols do
  285. for j=1,rows do
  286. r,g,b,a=img:get(i,j)
  287. if a>0 then
  288. c[i+2][j+2]=FILLED --indent two rows and cols to allow for border
  289. if firstBlankCol==0 then firstBlankCol=i+2 firstBlankRow=j+2 end
  290. end
  291. end
  292. end
  293. --now fill in outside of image, ie blank cells that are not enclosed by filled cells
  294. findEdge(c,firstBlankCol,firstBlankRow)
  295. --make path of edge cells, working anticlockwise
  296. path=FindEdgePath(c)
  297. --trim path to remove points within straight lines
  298. path=trimStraightLines(path)
  299. --create final path of vectors
  300. points=createPath(path,res)
  301. return points
  302. end
  303.  
  304. --this function works recursively to find all blank cells which are not embedded in the image. It does this
  305. --by starting with a blank cell outside the image, and looking at all neighbours. Any blank neighbours are
  306. --examined in turn.
  307. --Blank cells outside the image are labelled OUTERBLANK if they are not adjacent to a filled cell, or
  308. --UNUSEDEDGE if they are adjacent to a filled cell. These latter cells form the border around our image.
  309. function findEdge(c,i,j)
  310. c[i][j]=OUTERBLANK
  311. local filledNeighbor=false
  312. for ii=-1,1 do
  313. for jj=-1,1 do
  314. iii=i+ii
  315. jjj=j+jj
  316. if iii>0 and iii<=cols+4 and jjj>0 and jjj<=rows+4 then
  317. if c[iii][jjj]==EMPTY then
  318. findEdge(c,iii,jjj)
  319. elseif c[iii][jjj]==FILLED then
  320. filledNeighbor=true
  321. end
  322. end
  323. end
  324. end
  325. if filledNeighbor==true then c[i][j]=UNUSEDEDGE end
  326. end
  327.  
  328. --This function creates a path around the image using the chain of border cells identified above.
  329. --It has to work anticlockwise, which it does by examining the neighbours of the last cell in
  330. --the chain in a certain order, from lower left, anti clockwise round to the left. It loops
  331. --round twice, first at a distance of one cell, then two cells
  332. function FindEdgePath(c)
  333. path={} --this will be the array of vectors
  334. colA={ 0, 1, 0,-1, 0, 1, 0,-1} --these two rows specify the x,y offsets to look in
  335. rowA={-1, 0, 1, 0,-2, 0, 2, 0} --around the current cell
  336. i1=0
  337. --find first edge cell
  338. for i=1,cols+4 do
  339. for j=1,rows+4 do
  340. if c[i][j]==UNUSEDEDGE then i1,j1=i,j end
  341. end
  342. if i1>0 then break end
  343. end
  344. --add the first point
  345. n=1
  346. path[n]={i=i1,j=j1}
  347. c[i1][j1]=USEDEDGE
  348. --now work around the image anticlockwise
  349. i,j=i1,j1
  350. finished=false
  351. while finished==false do
  352. for u=1,#colA do
  353. iii,jjj=i,j
  354. ii=i+colA[u]
  355. jj=j+rowA[u]
  356. if ii>0 and ii<=cols+4 and jj>0 and jj<=rows+4 then
  357. if c[ii][jj]==UNUSEDEDGE then
  358. n = n + 1
  359. if n>1000 then
  360. print("ERROR - Infinite loop in creating outline")
  361. finished=true
  362. end
  363. path[n]={i=ii,j=jj}
  364. c[ii][jj]=USEDEDGE
  365. iii,jjj=ii,jj
  366. break
  367. elseif ii==i1 and jj==j1 and n> 5 then --are we done?
  368. finished=true
  369. break
  370. end
  371. end
  372. end
  373. if iii==i and jjj==j then
  374. --print("ERROR - Unable to complete outline")
  375. finished=true
  376. else
  377. i,j=iii,jjj
  378. end
  379. end
  380. return path
  381. end
  382.  
  383. --we only need to store the start and end cells for straight lines
  384. --This function removes cells in between
  385. function trimStraightLines(path)
  386. local i0,j0=0,0
  387. local ni,nj=0,0
  388. for p=#path,1,-1 do
  389. i,j=path[p].i,path[p].j
  390. if i==i0 then
  391. ni = ni + 1
  392. else
  393. if ni>2 then
  394. for u=p+ni-2,p+1,-1 do
  395. table.remove(path,p)
  396. end
  397. end
  398. ni=1
  399. i0=i
  400. end
  401. if j==j0 then
  402. nj = nj + 1
  403. else
  404. if nj>2 then
  405. for u=p+nj-2,p+1,-1 do
  406. table.remove(path,p)
  407. end
  408. end
  409. nj=1
  410. j0=j
  411. end
  412. end
  413. return path
  414. end
  415.  
  416. --This function creates the final vector array
  417. --it loops through the array, selecting points at least N pixels apart, where N is specified by the user
  418. --to balance accuracy and speed
  419. function createPath(path,N)
  420. NN=N*N --see below for reason for this
  421. local points={}
  422. table.insert(points,vec2(path[1].i,path[1].j))
  423. i0,j0=path[1].i,path[1].j
  424. for p=2,#path-1 do
  425. --skip points until we are at least r from the previous point
  426. --the calculation of distance between two points is the SQRT of the sum of square of the x and y diffs,
  427. --but to save doing the SQRT, we compare it with r squared instead
  428. if (path[p].i-i0)^2+(path[p].j-j0)^2>NN then
  429. table.insert(points,vec2(path[p].i,path[p].j))
  430. i0,j0=path[p].i,path[p].j
  431. end
  432. end
  433. return points
  434. end
  435.  
  436. function drawImage(img,c)
  437. local img2=image(cols+4,rows+4)
  438. for i=1,cols do
  439. for j=1,rows do
  440. r,g,b,a=img:get(i,j)
  441. if a>0 then img2:set(i+2,j+2,color(r,g,b,a)) end
  442. end
  443. end
  444. return img2
  445. end
  446.  
  447. function drawTestImage(c) --testing only
  448. local img2=image(cols+4,rows+4)
  449. for i=1,cols+4 do
  450. for j=1,rows+4 do
  451. if c[i][j]==UNUSEDEDGE then
  452. img2:set(i,j,color(255,255,255,255))
  453. elseif c[i][j]==OUTERBLANK then
  454. img2:set(i,j,color(0,0,255,50))
  455. elseif c[i][j]==FILLED then
  456. img2:set(i,j,color(255,0,0,100))
  457. elseif c[i][j]==USEDEDGE then
  458. img2:set(i,j,color(0,0,255,255))
  459. elseif c[i][j]==FLAG then
  460. img2:set(i,j,color(255,0,0,255))
  461. img2:set(i-1,j,color(255,0,0,255))
  462. img2:set(i+1,j,color(255,0,0,255))
  463. img2:set(i,j-1,color(255,0,0,255))
  464. img2:set(i,j+1,color(255,0,0,255))
  465. end
  466. end
  467. end
  468. for p=1,#path do
  469. i,j=path[p].i,path[p].j
  470. img2:set(i,j,color(255,255,0,255))
  471. end
  472. return img2
  473. end
  474.  
  475. function array1D(cols,defaultValue)
  476. if defaultValue==nil then defaultValue=0 end
  477. local A={}
  478. for i=1,cols do
  479. A[i]=defaultValue
  480. end
  481. end
  482.  
  483. function array2D(rows,cols,defaultValue,start)
  484. if defaultValue==nil then defaultValue=0 end
  485. if start==nil then start=1 end
  486. local A={}
  487. for i=start,rows do
  488. A[i]={}
  489. for j=start,cols do
  490. A[i][j]=defaultValue
  491. end
  492. end
  493. return A
  494. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement