fast .PNG tracing and outline collision !
Monkey Forums/User Modules/fast .PNG tracing and outline collision !
| ||
UPDATE 2: Made it a little faster and a float/int bug UPDATE 1: Now with collision detection and better polygon data. Small fix, and now with atlas support. Main target: ios/android/desktop Other targets: You need to get PNG data to extend this script with your target Its very simple, use a transparent png file, en this script get the outline. Then you can convert the outline to a polygon and use that for collision detection. Tested with several complex figures. Strict Import mojo Import opengl.gles11 Import classes.point Class Pngtracer Field outline:Stack<Point> Field simplyfied_outline:Stack<Point> Field data:Int[] Field verts:Float[] Method Trace:Stack<Point>(path:String, tolerance:Int=1,highestQuality:Bool=True, startX:Int=0, startY:Int=0, w:Int=-1, h:Int=-1) Local info:Int[2] Local db:DataBuffer = LoadImageData(path, info) Local startPixelX:Int = -1 Local startPixelY:Int = -1 If w=-1 Then w=info[0] If h=-1 Then h=info[1] data = data.Resize(w * h) For Local y:Int = 0 Until h For Local x:Int = 0 Until w Local j:Int = db.PeekInt( ( (startY + y) * info[0] + (startX + x)) * 4) data[y * w + x] = (j & $ff000000) | ( (j & $00ff0000) Shr 16) | (j & $0000ff00) | ( (j & $000000ff) Shl 16) If startPixelX=-1 And startPixelY=-1 And (data[y*w+x] shr 24) & $FF Then startPixelX=x startPixelY=y End Next Next db = Null squaresAlg(startPixelX,startPixelY,w,h) If highestQuality=False Then simplifyRadialDistance(outline,tolerance) simplifyDouglasPeucker(simplyfied_outline,tolerance) Else simplifyDouglasPeucker(outline,tolerance) End toPolyData(simplyfied_outline) Return outline End Method getSquareValue:Int(_x:Int,_y:Int, _w:Int, _h:Int) Local squareValue:Int=0 ' checking upper left pixel If (_y-1)*_w+(_x-1)>=0 And (data[(_y-1)*_w+(_x-1)] shr 24) & $FF Then squareValue+=1; End '' checking upper pixel If (_y-1)*_w+_x>=0 And (data[(_y-1)*_w+_x] shr 24) & $FF Then squareValue+=2; End '' checking left pixel If _y*_w+(_x-1)>=0 And _y*_w+(_x-1)<=data.Length And (data[_y*_w+(_x-1)] shr 24) & $FF Then squareValue+=4; End '' checking the pixel itself If _y*_w+_x>=0 And _y*_w+_x<=data.Length And (data[_y*_w+_x] shr 24) & $FF Then squareValue+=8; End Return squareValue End Method getSquareDistance:Float(p1:Point, p2:Point) Local dx:Float = p1.x - p2.x Local dy:Float = p1.y - p2.y Return dx * dx + dy * dy End Method simplifyRadialDistance:Stack<Point>(points:Stack<Point>, tolerance:Int=1) Local length:Int = points.Length() Local prev_point:Point = points.Get(0) Local new_points:Stack<Point> = New Stack<Point>() new_points.Push(prev_point) Local point:Point For Local i:Int = 0 Until length point = points.Get(i) If getSquareDistance(point, prev_point) > tolerance Then new_points.Push(point) prev_point = point End Next If prev_point <> point Then new_points.Push(point) End simplyfied_outline = new_points Return new_points End Method getSquareSegmentDistance:Float(p:Point, p1:Point, p2:Point) Local x:Float = p1.x Local y:Float = p1.y Local dx:Float = p2.x - x Local dy:Float = p2.y - y If dx <> 0 or dy <> 0 Then Local t:Float = ((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 += dx * t y += dy * t End End dx = p.x - x dy = p.y - y Return dx * dx + dy * dy End Method simplifyDouglasPeucker:Stack<Point>(points:Stack<Point>, tolerance:Int=1) Local length:Int = points.Length() Local markers:Int[] = New Int[length] Local f:Int = 0 Local l:Int = length-1 Local first_stack:IntStack = New IntStack() Local last_stack:IntStack = New IntStack() Local new_points:Stack<Point> = New Stack<Point>() markers[f] = 1 markers[l] = 1 Local index:Int = 0 Local max_sqdist:Float = 0 Local stop:Bool = False While stop=False max_sqdist = 0 For Local i:Int = f Until l Local sqdist:Float = getSquareSegmentDistance(points.Get(i), points.Get(f), points.Get(l)) If sqdist > max_sqdist Then index = i max_sqdist = sqdist End Next If max_sqdist > tolerance Then markers[index] = 1 first_stack.Push(f) last_stack.Push(index) first_stack.Push(index) last_stack.Push(l) End If first_stack.IsEmpty() Then f = 0 Else f = first_stack.Pop() End If last_stack.IsEmpty() Then l = 0 stop = True Else l = last_stack.Pop() End End For Local i:Int = 0 Until length If markers[i] Then new_points.Push(points.Get(i)) End Next simplyfied_outline = new_points Return new_points End Method squaresAlg:Void(_x:Int,_y:Int,_w:Int,_h:Int) Local pX:Int=_x Local pY:Int=_y Local stepX:Int Local stepY:Int Local prevX:Int Local prevY:Int Local closedLoop:Bool=False outline = New Stack<Point>() While closedLoop=False Select getSquareValue(pX,pY,_w,_h) Case 1,5,13 stepX=0 stepY=-1 Case 8,10,11 stepX=0 stepY=1 Case 4,12,14 stepX=-1 stepY=0 Case 2,3,7 stepX=1 stepY=0 Case 6 If prevX=0 And prevY=-1 Then stepX=-1 stepY=0 Else stepX=1 stepY=0 End Case 9 If prevX=1 And prevY=0 Then stepX=0 stepY=-1 Else stepX=0 stepY=1 End End pX+=stepX pY+=stepY outline.Push(new Point(pX, pY)) prevX=stepX prevY=stepY If pX=_x And pY=_y Then closedLoop=true End End End Method toPolyData:Float[](points:Stack<Point>) verts = verts.Resize(points.Length*2) Local tmpI:Int = 0 For Local point:Point = Eachin points verts[tmpI] = point.x tmpI=tmpI+1 verts[tmpI] = point.y tmpI=tmpI+1 Next Return verts End End example Strict Import mojo Import opengl.gles11 Import classes.pngtracer Function Main:Int() New MyApp() Return 0 End Class MyApp Extends App Field thing:Sprite Method OnCreate:Int() SetUpdateRate(60) thing = New Sprite() Return 0 End Method OnUpdate:Int() thing.Update() Return 0 End Method OnRender:Int() Cls(255,255,255) thing.Draw() Return 0 End End Class Sprite Field visible_outline:Stack<Point> = New Stack<Point>() Field simplyfied_outline:Stack<Point> = New Stack<Point>() Field verts:Float[] Field hit:Bool Field imgSlice:Image Method New() Local PNGTrace:Pngtracer = New Pngtracer() PNGTrace.Trace("monkey://data/test-thing2.png",1,True) visible_outline = PNGTrace.outline simplyfied_outline = PNGTrace.simplyfied_outline verts = PNGTrace.verts ' or atlas file '' PNGTrace.Trace("monkey://data/atlas-world1.png",1,True,660,1207,242,132) '' visible_outline = PNGTrace.outline '' simplyfied_outline = PNGTrace.simplyfied_outline '' verts = PNGTrace.verts '' imgSlice = CreateImage(242, 132) '' imgSlice.WritePixels(PNGTrace.data, 0, 0, 242, 132) ' ! I WILL USE IT LIKE THIS ' I save the verts in a json file, so I don't have to calculate it before ' Monkey has a bug or it yust don't want to work with my poly data, I don't know why yet. ' DrawPoly(verts) don't work, but hit detection and other poly math do work. ' End Method Draw:Void() '' DrawImage(imgSlice,0,0) If hit Then SetColor(255,0,0) For Local point:Point = Eachin simplyfied_outline DrawPoint(point.x,point.y) Next Else SetColor(0,0,255) For Local point:Point = Eachin visible_outline DrawPoint(point.x,point.y) Next End ' DrawPoly(verts) '<--- MONKEY fails, but the poly data is correct !!! End Method Update:Void() If PointInPoly(MouseX(),MouseY(),verts) Then hit = True Else hit = False End End Function PointInPoly:Bool(x:Float, y:Float, poly:Float[]) Local i:Int, j:Int, c:Bool Local v1:Bool, v2:Bool, v3:Bool, v4:Bool, v5#, v6#, v7# c = False Local p_count% = (poly.Length() / 2) For i = 0 To p_count-1 j = (i+1) Mod p_count v1 = (poly[i*2+1] <= y) v2 = (y < poly[j*2+1]) v3 = (poly[j*2+1] <= y) v4 = (y < poly[i*2+1]) v5 = (poly[j*2]-poly[i*2]) * (y-poly[i*2+1]) v6 = (poly[j*2+1]-poly[i*2+1]) If v6 = 0.0 Then v6 = 0.0001 v7 = poly[i*2] If (((v1 And v2) Or (v3 And v4)) And (x < v5 / v6 + v7)) Then c = Not c Next Return c End End |
| ||
Looks very handy! |
| ||
Oh, that's some nice code! Great result with this test: ![]() |
| ||
within some time i will post the outline points2poly code to check a collision. i have to find a code to convert all the coordinates to clockwise polygon verts. |