Convex hull: quickhull algorithm

Monkey Forums/Monkey Code/Convex hull: quickhull algorithm

Warpy(Posted 2011) [#1]
I've just ported this over from bmax because I needed it for something, so here you go:

This code will find the convex hull of a set of 2d points. The convex hull is the smallest convex polygon which contains all the given points.

The function quickhull returns a list of points which are the vertices of the hull, in clockwise order.


Class Point
	Field x#,y#
	
	Method New(_x#,_y#)
		x=_x
		y=_y
	End
End

'returns True if p1 and p2 are on the same side of the line a->b
Function sameside(p1x#,p1y#,p2x#,p2y#,ax#,ay#,bx#,by#)
	Local cp1# = (bx-ax)*(p1y-ay)-(p1x-ax)*(by-ay)
	Local cp2# = (bx-ax)*(p2y-ay)-(p2x-ax)*(by-ay)
	If cp1*cp2 >= 0 Then Return True
End Function	
	
'Clever little trick for telling if a point is inside a given triangle
'If for each pair of points AB in the triangle, P is on the same side of AB as 
'the other point in the triangle, then P is in the triangle. 
Function pointintriangle(px#,py#,ax#,ay#,bx#,by#,cx#,cy#)
	Return (sameside(px,py,ax,ay,bx,by,cx,cy) And sameside(px,py,bx,by,ax,ay,cx,cy) And sameside(px,py,cx,cy,ax,ay,bx,by))
End Function

'Quickhull function - call this one with a set of points.
Function quickhull:List<Point>(s:List<Point>)
	If s.Count()<=3 Return s
	Local l:Point=Null
	Local r:Point=Null
	Local p:Point
	
	'get left and right bounds
	For p=Eachin s
		If l=Null
			l=p
		Elseif p.x<l.x
			l=p
		Endif
		If r=Null
			r=p
		Elseif p.x>r.x
			r=p
		Endif
	Next
	
	Local an#=ATan2(r.y-l.y,r.x-l.x)
	Local rx#=Cos(an)
	Local ry#=Sin(an)
	Local sx#=Cos(an+90)
	Local sy#=Sin(an+90)
	
	Local s1:=New List<Point>
	Local s2:=New List<Point>
	For p=Eachin s
		If p<>l And p<>r
			Local mu#=(l.y-p.y+(ry/rx)*(p.x-l.x))/(sy-sx*ry/rx)
			If mu<0 
				s1.AddLast p 
			Elseif mu>0
				s2.AddLast p
			Endif
		Endif
	Next
	
	Local out1:=findhull(s1,l,r)
	Local out2:=findhull(s2,r,l)
	Local out:= New List<Point>
	out.AddLast l
	If out1
		For p=Eachin out1
			out.AddLast p
		Next
	Endif
	out.AddLast r
	If out2
		For p=Eachin out2
			out.AddLast p
		Next
	Endif
	
	Return out
End Function

'Findhull helper function - you never need to call this
Function findhull:List<Point>(sk:List<Point>,p:Point,q:Point)
	If sk.Count()=0 Return Null
	Local c:Point=Null
	Local out:=New List<Point>
	Local maxdist#=-1
	Local an#=ATan2(q.y-p.y,q.x-p.x)
	Local rx#=Cos(an)
	Local ry#=Sin(an)
	Local sx#=-ry
	Local sy#=rx
	Local mu#
	
	Local tp:Point
	For tp=Eachin sk
		If tp<>p And tp<>q
			mu=Abs((p.y-tp.y+(ry/rx)*(tp.x-p.x))/(sy-sx*ry/rx))
			If maxdist=-1 Or mu>maxdist
				c=tp
				maxdist=mu
			Endif
		Endif
	Next
	
	an=ATan2(c.y-p.y,c.x-p.x)
	rx=Cos(an)
	ry=Sin(an)
	sx=Cos(an+90)
	sy=Sin(an+90)
	Local s1:=New List<Point>
	Local s2:=New List<Point>
	For tp=Eachin sk
		If tp<>c
			If Not pointintriangle(tp.x,tp.y,p.x,p.y,q.x,q.y,c.x,c.y)
				mu=(p.y-tp.y+(ry/rx)*(tp.x-p.x))/(sy-sx*ry/rx)
				If mu<0 
					s1.AddLast tp 
				Elseif mu>0 
					s2.AddLast tp
				Endif
			Endif
		Endif
	Next
	Local out1:=findhull(s1,p,c)
	Local out2:=findhull(s2,c,q)
	If out1
		For p=Eachin out1
			out.AddLast p
		Next
	Endif
	out.AddLast c
	If out2
		For p=Eachin out2
			out.AddLast p
		Next
	Endif
	Return out
End Function



CodeGit(Posted 2011) [#2]
Thanks.


xzess(Posted 2011) [#3]
Thanks Warpy, that might be really useful!