local sdist = function (x1,y1, x2,y2) return ((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1)) end 
local dist = function (x1,y1, x2,y2) return ((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1))^0.5 end
local abs = math.abs
local sign = math.sign
local min,max,sqrt = math.min,math.max,math.sqrt

ffi.cdef(([[
  typedef struct { cpml_vec2 a, b, c;} HullTriangle;
]]))
local segmentBuf  = ffi.new(VECTOR_TYPE.."[32000][20]",{{0}})
local tempSeg = ffi.new(VECTOR_TYPE.."[20]")
local strokeSeg = {}

local function cross(points, b, c, normal) 
  local bx = b.x
  local by = b.y
  local cyby = c.y - by
  local cxbx = c.x - bx
  local pa = points.a
  local pb = points.b
  local pc = points.c
    return not (
      (((pa.x - bx) * cyby - (pa.y - by) * cxbx) * normal < 0) or
      (((pb.x - bx) * cyby - (pb.y - by) * cxbx) * normal < 0) or
      (((pc.x - bx) * cyby - (pc.y - by) * cxbx) * normal < 0))
end

local function trianglesIntersect(t0, t1) 
	local normal0 = (t0.b.x-t0.a.x)*(t0.c.y-t0.a.y)-
              (t0.b.y-t0.a.y)*(t0.c.x-t0.a.x);
	local normal1 = (t1.b.x-t1.a.x)*(t1.c.y-t1.a.y)-
              (t1.b.y-t1.a.y)*(t1.c.x-t1.a.x);
              
  return not (
  	cross(t1, t0.a, t0.b, normal0) or
    cross(t1, t0.b, t0.c, normal0) or
    cross(t1, t0.c, t0.a, normal0) or
    cross(t0, t1.a, t1.b, normal1) or
    cross(t0, t1.b, t1.c, normal1) or
    cross(t0, t1.c, t1.a, normal1))
end

local function hullTriangles(seg,tri1,tri2)
	-- use one of two triangulations depending on if control points on same side of endpoint line
	local sign1,sign2 = 
		math.sign(distPointToLineSegment2(seg[5],seg[6],seg[1],seg[2], seg[3],seg[4], true,true)),
		math.sign(distPointToLineSegment2(seg[7],seg[8],seg[1],seg[2], seg[3],seg[4], true,true))

	tri1.a.x, tri1.a.y = seg[1],seg[2]
	tri1.b.x, tri1.b.y = seg[5],seg[6]
	tri1.c.x, tri1.c.y = seg[3],seg[4]
	if sign1==sign2 then 
		tri2.a.x, tri2.a.y = seg[5],seg[6]
		tri2.b.x, tri2.b.y = seg[7],seg[8]
		tri2.c.x, tri2.c.y = seg[3],seg[4]
	else
		tri2.a.x, tri2.a.y = seg[1],seg[2]
		tri2.b.x, tri2.b.y = seg[3],seg[4]
		tri2.c.x, tri2.c.y = seg[7],seg[8]
	end
end

local t1l,t1r = ffi.new("HullTriangle"),ffi.new("HullTriangle")
local t2l,t2r = ffi.new("HullTriangle"),ffi.new("HullTriangle")
local function intersectHulls(seg1,seg2)
	hullTriangles(seg1,t1l,t1r)
	hullTriangles(seg2,t2l,t2r)
	return  
		trianglesIntersect(t1l,t2l) or 
		trianglesIntersect(t1l,t2r) or 
		trianglesIntersect(t1r,t2l) or 
		trianglesIntersect(t1r,t2r) 
end

local function segFlat(seg)
	return 
		(distPointToLineSegment2(seg[5],seg[6],seg[1],seg[2],seg[3],seg[4],true) +
		 distPointToLineSegment2(seg[7],seg[8],seg[1],seg[2],seg[3],seg[4],true))<0.4
end

local function splitCurve(source,left,right)
	left[1],left[2] = source[1],source[2] -- A
	left[5],left[6] = (source[1]+source[5])/2,(source[2]+source[6])/2 -- E
	left[7],left[8] = (source[5]+source[7])/2,(source[6]+source[8])/2 -- F
	left[3],left[4] = (source[3]+source[7])/2,(source[4]+source[8])/2 -- G
	left[3],left[4] = (left[3]+left[7])/2,(left[4]+left[8])/2 -- J
	
	right[5],right[6] = left[3],left[4]

	left[7],left[8] = (left[5]+left[7])/2,(left[6]+left[8])/2 -- H
	left[3],left[4] = (left[3]+left[7])/2,(left[4]+left[8])/2 -- K
	left[12],left[13] = source[12],(source[12]+source[13])/2 -- t

	right[1],right[2] = left[3],left[4] -- K
	right[3],right[4] = source[3],source[4] -- D
	right[7],right[8] = (source[3]+source[7])/2,(source[4]+source[8])/2 -- G

	right[12],right[13] = (source[12]+source[13])/2,source[13] -- t
end

local function lerp(a,b,t) return (1-t)*a + t*b end
local tmp = ffi.new(VECTOR_TYPE.."[20]")
local tmpCurve = ffi.new(VECTOR_TYPE.."[20]")
function sliceCurve(source,slice,a,b)
	tmp[1],tmp[2] = lerp(source[1],source[5],b),	lerp(source[2],source[6],b) -- E
	tmp[3],tmp[4] = lerp(source[5],source[7],b),	lerp(source[6],source[8],b) -- F
	tmp[5],tmp[6] = lerp(source[7],source[3],b),	lerp(source[8],source[4],b) -- G
	tmp[7],tmp[8] = lerp(tmp[3],tmp[5],b),				lerp(tmp[4],tmp[6],b) -- J

	slice[1],slice[2] = source[1],source[2] -- A
	slice[5],slice[6] = tmp[1],tmp[2] -- E
	slice[7],slice[8] = lerp(tmp[1],tmp[3],b),		lerp(tmp[2],tmp[4],b) -- H
	slice[3],slice[4] = lerp(slice[7],tmp[7],b),	lerp(slice[8],tmp[8],b) -- K


	local c = a/b
	tmp[1],tmp[2] = lerp(slice[1],slice[5],c),	lerp(slice[2],slice[6],c) -- E
	tmp[3],tmp[4] = lerp(slice[5],slice[7],c),	lerp(slice[6],slice[8],c) -- F
	tmp[5],tmp[6] = lerp(slice[7],slice[3],c),	lerp(slice[8],slice[4],c) -- G
	tmp[7],tmp[8] = lerp(tmp[1],tmp[3],c),				lerp(tmp[2],tmp[4],c) -- H

	slice[5],slice[6] = lerp(tmp[3],tmp[5],c),	lerp(tmp[4],tmp[6],c) -- J
	slice[1],slice[2] = lerp(tmp[7],slice[5],c),	lerp(tmp[8],slice[6],c) -- K
	slice[3],slice[4] = slice[3],slice[4] -- D
	slice[7],slice[8] = tmp[5],tmp[6] -- G

	slice[10],slice[11] = source[10],source[11]
	slice[12],slice[13] = a,b -- t
end

local splitSegs = ffi.new(VECTOR_TYPE.."[64000][20]")
local bezTests = 0
local function intersectBeziers(seg1,seg2,intersections,n)
	n[1] = n[1]+1
	bezTests = bezTests+1
	local sflat1,sflat2 = segFlat(seg1), segFlat(seg2)
	if (sflat1 and sflat2)  then
		local x,y = 
		-- (seg1[1]+seg1[3]+seg2[1]+seg2[3])/4, (seg1[2]+seg1[4]+seg2[2]+seg2[4])/4
		intersect( seg1[1], seg1[2], seg1[3], seg1[4], seg2[1], seg2[2], seg2[3], seg2[4])
		if x then 
			local t1 = lerp(seg1[12],seg1[13],dist(seg1[1], seg1[2],x,y)/dist(seg1[1], seg1[2],seg1[3], seg1[4]))
			local t2 = lerp(seg2[12],seg2[13],dist(seg2[1], seg2[2],x,y)/dist(seg2[1], seg2[2],seg2[3], seg2[4]))
			table.insert(intersections,{x,y,t1,t2})
		end
	else
		if intersectHulls(seg1,seg2) then 
			n[2] = n[2]+1
			local a1,a2 = seg1
			local b1,b2 = seg2

			if not sflat1 then 
				splitSegs[0][0] = splitSegs[0][0] + 2
				a1,a2 = splitSegs[splitSegs[0][0]-1],  splitSegs[splitSegs[0][0]]
				splitCurve(seg1,a1,a2)
			end

			if not sflat2 then 
				splitSegs[0][0] = splitSegs[0][0] + 2
				b1,b2 = splitSegs[splitSegs[0][0]-1],splitSegs[splitSegs[0][0]]
				splitCurve(seg2,b1,b2)
			end

												intersectBeziers(a1,b1,intersections,n)
			if b2        then intersectBeziers(a1,b2,intersections,n) end
			if a2 			 then intersectBeziers(a2,b1,intersections,n) end
			if a2 and b2 then intersectBeziers(a2,b2,intersections,n) end
		end
	end
end


local function max4(a,b,c,d)
	return max(max(max(a,b),c),d)
end

local function max9(a,b,c,d,e,f,g,h,i)
	return max(max(max(max(max(max(max(max(a,b),c),d),e),f),g),h),i)
end

local function min4(a,b,c,d)
	return min(min(min(a,b),c),d)
end

local function min9(a,b,c,d,e,f,g,h,i)
	return min(min(min(min(min(min(min(min(a,b),c),d),e),f),g),h),i)
end


local function addSegToGrid(grid,s,split)
	local seg = segmentBuf[s]  
	for g=1,#grid,1 do 
  	if  grid[g][2][1] <= max4(seg[1],seg[3],seg[5],seg[7]) and
        grid[g][2][3] >= min4(seg[1],seg[3],seg[5],seg[7]) and
        grid[g][2][2] <= max4(seg[2],seg[4],seg[6],seg[8]) and
        grid[g][2][4] >= min4(seg[2],seg[4],seg[6],seg[8]) then 
      table.insert(grid[g][1],seg)
    end
  end
end
local function segSort(a,b)
	return a[3] < b[3] 
end

function segmentStrokes(segments,strokes,ends)

	local segmentN 	= 0

	-- if not strokes[1] then return end
	local bbox = {
		l=strokes[1].line[1][1],
		t=strokes[1].line[1][2],
		r=strokes[1].line[1][1],
		b=strokes[1].line[1][2],
	}

	for s=1,#strokes,1 do
		if strokes[s].group == "line" then 
			local line = strokes[s].line
			ends[s][0]=0
			for l = 1, #line-1, 1 do 
				segmentN = segmentN +1
				local seg = segments[segmentN]
				-- end points
				seg[1] = line[l][1]
				seg[2] = line[l][2]
				seg[3] = line[l+1][1]
				seg[4] = line[l+1][2]
				-- control points
				seg[5] = line[l][5] 	
				seg[6] = line[l][6] 	
				seg[7] = line[l+1][3] 
				seg[8] = line[l+1][4] 
				-- info
				seg[9] = segmentN
				seg[10] = s
				seg[11] = l
				-- end t values
				seg[12] = 0
				seg[13] = 1
				-- bounds
				seg[14] = min4(seg[1],seg[3],seg[5],seg[7])
				seg[15] = min4(seg[2],seg[4],seg[6],seg[8])
				seg[16] = max4(seg[1],seg[3],seg[5],seg[7])
				seg[17] = max4(seg[2],seg[4],seg[6],seg[8])
			end
			
			for l = 1, #line, 1 do 
		    bbox.l=min(bbox.l,line[l][1]-0.1)
				bbox.t=min(bbox.t,line[l][2]-0.1)
		    bbox.r=max(bbox.r,line[l][1]+0.1)
		    bbox.b=max(bbox.b,line[l][2]+0.1)
			end
		end
	end



  bbox.w = (bbox.r-bbox.l)
  bbox.h = (bbox.b-bbox.t)


	segments[0][0] = segmentN

	return segments,bbox
end

local function sweepSort(a,b)
	return a[2]==b[2] and a[1]<b[1] or a[2]<b[2] 
end
local function updateEnds(ende,seg,trim)
	if not trim then return end
	if ende[0] > 0 then 
		ende[0]=ende[0]+1
		ende[1]=seg[11]<ende[1] and seg[11] or ende[1]
		ende[2]=seg[11]>ende[2] and seg[11] or ende[2]
		if seg[11]==ende[1] and seg[12] <ende[3] then 
			ende[3],	ende[5],ende[6]= seg[13], seg[3],seg[4]
		end
		if seg[11]==ende[2] and seg[13]>ende[4] then 
			ende[4],	ende[7],ende[8]= seg[12], seg[1],seg[2]
		end
	else
		ende[0]=1
		ende[1], ende[2], ende[3], ende[4],ende[5], ende[6], ende[7], ende[8]= 
		seg[11], seg[11], seg[13], seg[12],seg[1], seg[2], seg[3], seg[4]
	end
end
function trimTips(strokes,trim)
	



	-------------------------------------------------
	-- TODO 
	-- REMOVE SEGMENTS W/ 2 SHARED FILL SEGS 
	-- AVOIDS RETRIANGULATING ALREADY FILLED SHAPES
	-------------------------------------------------




	print("\nstart trim---------------\n")
	_time("  start",false)
	collectgarbage("stop")
	local ends = ffi.new(VECTOR_TYPE.."[?][9]",#strokes+1)
	-- first and last index and t value

	local segments = segmentBuf
	local _,bbox = segmentStrokes(segments,strokes,ends)

	local segmentN = segments[0][0]
	_time("  time2")

	local checks = {}
	local sweepPoints = {}
	for i=1, segmentN ,1 do
		table.insert(sweepPoints,{0,segments[i][14],i})
		table.insert(sweepPoints,{1,segments[i][16],i})
	end
	table.sort(sweepPoints,sweepSort)
	local active = {}
	for i=1, #sweepPoints ,1 do
		local pt = sweepPoints[i]
		-- print("pt",pt[1],pt[2],pt[3])
		if pt[1]==0 then 
			for a=1,#active,1 do 
				local seg1,seg2 = segments[pt[3]],segments[active[a]]
				if active[a]~=pt[3] and not(seg1[10]==seg2[10] and (seg1[11]==seg2[11]-1 or seg1[11]==seg2[11]+1)) and seg1[17] > seg2[15] and seg1[15] < seg2[17] then 
					table.insert(checks,active[a])
					table.insert(checks,pt[3])
				end
			end
			table.insert(active,pt[3])
		else 
			lume.remove(active,pt[3])
		end
	end

	print("	checks",#checks/2,#active)

	_time("  time3")

	local segCuts={}
	local segTests = 0
	local inters = 0
	bezTests = 0
	splitSegs[0][0]=0
	local segInts 
	for c=1,#checks-1,2 do 
		local seg1 = segments[checks[c]]
		local seg2 = segments[checks[c+1]]
		local s1,  s2   = seg1[9],seg2[9]
		local intersections = {}
		segTests = segTests+1
		local count = {0,0}
		intersectBeziers(seg1,seg2,intersections,count)
		-- if count[2]>0 then 
			-- editor.addTestLines({{seg1[1],seg1[2],seg1[5],seg1[6],seg1[7],seg1[8],seg1[3],seg1[4]}})
			-- editor.addTestLines({{seg2[1],seg2[2],seg2[5],seg2[6],seg2[7],seg2[8],seg2[3],seg2[4]}})
		-- end
		if #intersections>0 then 
			for i,int in ipairs(intersections) do
				inters = inters + 1
				local x,y,t1,t2 = int[1],int[2],int[3],int[4]
				local s = 0.3
				-- editor.addTestLines({{x-s,y,x,y-s,x+s,y,x,y+s}})
		  	segCuts[s1]= segCuts[s1] or {}
		  	table.insert(segCuts[s1],{ x, y, t1 })
		  	segCuts[s2]= segCuts[s2] or {}
		  	table.insert(segCuts[s2],{ x, y, t2 })
			end
		end
	end

	print("segTests",segTests)
	print("bezTests",bezTests)
	print("inters",inters)

	_time("  time4")

	
	local segn = segmentN
	for s=1,segmentN,1 do 
		if segCuts[s] then 
			table.sort(segCuts[s],segSort)
			local t=0
			local x, y = segments[s][1],segments[s][2]
			for c=1,#segCuts[s],1 do 
				local cut = segCuts[s][c]
				segmentN = segmentN + 1
				local newSeg = segments[segmentN]
				sliceCurve(segments[s],newSeg,t,cut[3])
				newSeg[1],newSeg[2]=x,y
				-- editor.addTestLines({{x,y,cut[1],cut[2],x,y}})
				x,y = cut[1],cut[2]
				newSeg[3],newSeg[4] = x,y
				t=cut[3]
				updateEnds(ends[newSeg[10]],newSeg,trim)
				-- editor.addTestLines({{newSeg[1],newSeg[2],newSeg[3],newSeg[4],newSeg[1],newSeg[2]}})
			end
			local seg = segments[s]
			sliceCurve(segments[s],tmpCurve,t,1)
			tmpCurve[3],tmpCurve[4]=segments[s][3],segments[s][4]
			segments[s]=tmpCurve
			segments[s][1],segments[s][2]=x,y

			updateEnds(ends[seg[10]],segments[s],trim)
			-- editor.addTestLines({{segments[s][1],segments[s][2],segments[s][3],segments[s][4],segments[s][1],segments[s][2]}})
		end
	end

	-- cut ends
	-- for s=1,#strokes,1 do 
	-- 	if ends[s][0]>2 then 
	-- 		local x1,y1,s1 = ends[s][5],ends[s][6],1
	-- 		local x2,y2,s2 = ends[s][7],ends[s][8],0.5
	-- 		editor.addTestLines({{x1-s1,y1,x1,y1-s1,x1+s1,y1,x1,y1+s1},{x2-s2,y2,x2,y2-s2,x2+s2,y2,x2,y2+s2}})
	-- 	end
	-- end
	if trim then 
		local cuts =0
		for s=segmentN,1,-1 do 
			local seg = segments[s]
			local ende = ends[seg[10]]
			local cut 
			if (ende[0]<3 or seg[11]<ende[1] or seg[11]>ende[2])
					or (seg[11]==ende[1] and seg[12]<ende[3]) 
					or (seg[11]==ende[2] and seg[13]>ende[4])  then
				cut = true
			end

			if cut then 
				cuts = cuts +1
				-- editor.addTestLines({{seg[1],seg[2],seg[3],seg[4]}})
				segments[s]=segments[segmentN]
				segmentN=segmentN-1
			end
		end
	end
	print("cut segs",cuts,"/",segmentN)

	segments[0][0]=segmentN

	_time("  time5")

	print("  segments ",tostring(segments[0][0]))

	print("\nend trim---------------\n")
	collectgarbage("restart")
	return segments,bbox
end



local function close(segs,edge,segments)
	local seg
	for s=1,#segs,1 do 
		seg = segments[segs[s]]
		local d1,d2 = 
			distPointToLineSegment({edge.p1.x, edge.p1.y},{seg[1], seg[2]},{seg[3], seg[4]}), 
			distPointToLineSegment({edge.p2.x, edge.p2.y},{seg[1], seg[2]},{seg[3], seg[4]})
		if d1<0.1 and d2<0.1  then 
    	return true 
    end
	end
	return false
end
local function trit(t)
  return {t[1],t[2],t[3],t[4],t[5],t[6]}
end
local function getFills(strokes)
	local fills = {}
	for s,stroke in ipairs(strokes) do
		if stroke.group=="fill" then 
			table.insert(fills,stroke)
		end
	end 
	return fills
end

local function triBuffer (shapes)
	local size = 0
	for s,shape in ipairs(shapes)do 
		for l,line in ipairs(shape.line)do
			size=size+#line*32
		end
	end
	return ffi.new(string.format(VECTOR_TYPE.."[%i][8]",size))
end

local function cachedLine(panel,shape)
	local cache = shape.group and  panel.cachedStrokes or panel.cachedFills
	print(shape.index)
	print(cache[shape.index])
	cache[shape.index] = cache[shape.index] or fillOutline(shape.line,1)
	return cache[shape.index]
end

local function pointInFill(panel,shape,point,circle)
 	local line = cachedLine(panel,shape)
 	local x,y = 
 		point[3] and (point[1]+point[3])/2 or point[1],
 		point[3] and (point[2]+point[4])/2 or point[2]
	if PointWithinShape(line, x,y) then 
		return true
	end
	if circle and point[5]>0.1 then 
		for l=1,#line-2,2 do 
			if distPointToLineSegment2(x,y, line[l], line[l+1],line[l+2], line[l+3]) < point[5] then 
				return true
			end
		end
	end
	return false
end

function editor.prepareFill(panel,segments,bbox)

	local count = panel.lastFill and panel.lastFill.count

	if not segments then 
		segments,bbox = trimTips(panel.f.strokes,drawingPanel.fillGap==0)
	end
	panel.proccedStrokes = {segments,bbox}

	-- new fill coroutine
	if panel.threadFill then 
		panel.fillThread = coroutine.create(function()
			print("start fill --------------")
			coroutine.yield()
			collectgarbage("stop")
			print("set fill frame ",panel.f.id)
			panel.lastFrame=panel.f.id
			panel.lastFill=fillVector(panel,segments,bbox)
			panel.filledFrame = false
			collectgarbage("restart")
			print("end fill --------------")
	  end)
		print("resume")
		print(coroutine.resume(panel.fillThread))
	end

	return panel.proccedStrokes
end

local dpoints = {}
function editor.addTestLines(testpolys)
	if (not testpolys) or (#testpolys==0) or not animationPanel.debugView then return end
	local o =0
	for p,poly in ipairs(testpolys) do
		if #poly>1 then 
			local ox,oy = (-o+math.random()*o),(-o+math.random()*o)
			local poly2 = {color = poly.color }
			if type(poly[1])~="table" then 
				for p=1,#poly-1,2 do 
					table.insert(poly2,{poly[p],poly[p+1]})
				end
			else 
				for p=1,#poly,1 do
					if poly[p][3]~=0 then table.insert(poly2,{poly[p][3],poly[p][4]}) end
					table.insert(poly2,{poly[p][1],poly[p][2]})
					if poly[p][5]~=0 then table.insert(poly2,{poly[p][5],poly[p][6]}) end
				end	
			end
			poly=poly2
			-- table.insert(poly,poly[1])
			for p=1,#poly,1 do 
				poly[p][1],poly[p][2] = poly[p][1]+ox*math.random(),  poly[p][2]+oy*math.random()
			end

			-- print(inspect(poly))
			local randomColor = {r=math.random(),g=math.random(),b=math.random(),a=1}
			table.insert(animationPanel.f.strokes,{size=0.1, debug=true,ends=1, color=poly.color or randomColor, group="note", line=poly})
		end
	end
end
function editor.fillTool(panel,moving)
	if moving and not mouse.moved then return end
	if panel.pickingColor then return end

	editor.pushColor(drawingPanel)

	drawingPanel.brush.color = lume.clone(drawingPanel.color)

	local fw,fh
	local x,y 
	local res = panel.l.resolution
	local scale = 1 
	local padding

	if panel.l.format~="vector" then 
		editor.expandCanvas (panel)
	end

	local startPoint
	local startFill

	local segments,bbox

	if panel.l.format=="vector" then 
		if not panel.f.strokes or #panel.f.strokes==0 then 
			editor.tempTip("No Strokes To Fill")
			return 
		end

		print(" filled frame ", panel.lastFrame,panel.f.id)
		if (panel.lastFill and panel.lastFrame~=panel.f.id) then
			panel.lastFill=nil
			print("CHANGE")
		end
		if  (not moving and not panel.proccedStrokes) then 
			editor.prepareFill(panel)
		end
		segments,bbox= unpack(panel.proccedStrokes)
		startFill=true
		startPoint={x=panel.brushCoords.x*res, y=panel.brushCoords.y*res}
		panel.fillStartPoint = startPoint
	else  
		startPoint= {x=mf(panel.brushCoords.x), y=mf(panel.brushCoords.y)} 
		panel.fillDims = {w=panel.f.w,h=panel.f.h}
		startFill = editor.resetFill (panel,panel.f.canvas,startPoint)
	end

	local filled

	if startFill then 
		
		if panel.l.format=="vector" then 
			collectgarbage("stop")
			print("\nFILL VECTOR --------")
			_time()
			panel.lastFill = panel.lastFill or {}
			local skipFill
			local r = drawingPanel.fillRadius
			local startBox = {startPoint.x-r,startPoint.y-r,startPoint.x+r,startPoint.y+r,r/2}
			
			for s,stroke in lume.ripairs(panel.f.strokes) do
				if stroke.debug then
					table.remove(panel.f.strokes,s)
				end
			end

			local fills = 0
			for s,stroke in ipairs(panel.f.strokes) do
				stroke.index = s
				if stroke.group =="fill" and stroke.line[1] and type(stroke.line[1]) == "table" and #stroke.line[1]>2   then fills = fills+1
					if not stroke.bbox then 
						local line = cachedLine(panel,stroke)
						stroke.bbox = {line[1],line[2],line[1],line[2]}
						for i=1,#line-1,2 do 
							stroke.bbox[1] = min(stroke.bbox[1], line[i])
							stroke.bbox[2] = min(stroke.bbox[2], line[i+1])
							stroke.bbox[3] = max(stroke.bbox[3], line[i])
							stroke.bbox[4] = max(stroke.bbox[4], line[i+1])
						end
					end
					
					if bboxCollide1 (startBox, stroke.bbox) and pointInFill(panel,stroke,startBox,true) then 
						-- local randomColor = {r=math.random()/4+0.6,g=math.random()/3+0.4,b=math.random()/3+0.4,a=1}
						local r1,g1,b1,a1 = unpackColor(randomColor or drawingPanel.brush.color)
						local r2,g2,b2,a2 = unpackColor(stroke.color)
						if not (r1==r2 and g1==g2 and b1==b2 and a1==a2) then 
							stroke.color = lume.clone(randomColor or drawingPanel.brush.color)
							editor.refreshStrokes()
						end
						if startBox[5]==0 then 
							skipFill = true
							break
						end
					end
				end
			end
			_time("recolor fills")

			if not skipFill then 
				local startTime = ltimer.getTime()

				if not moving then 
					if panel.threadFill then 
						if not panel.forceRefill and panel.fillThread and coroutine.status(panel.fillThread)~="dead" then 	
							panel.finishFill = true
							print("FINISH FILL")
							coroutine.resume(panel.fillThread)
							print("FILL FINISHED")
						end
					else 
						panel.lastFill=fillVector(panel,segments,bbox) or panel.lastFill or {}
					end
				end
				_time("gen fills")

				panel.finishFill = false
				

				local shapes=panel.lastFill.shapes
				local count=panel.lastFill.count
				local testpolys=panel.lastFill.testpolys

				-- remove possible fill region if an existing fill exists in the frame that matchs(~) it


					

				_time("reduce")
				print("fills",fills)

		  	if shapes and shapes[1]  then 
					print("shapes",#shapes)
					table.insert(testpolys,shapes[1])
					for s,shape in ipairs(shapes) do 
						shape.index=s
						if  (not panel.fillsUsed[s]) and (drawingPanel.fillAll or (bboxCollide1 (startBox, shape.bbox) and  pointInFill(panel,shape,startBox,true))) then
							local line = cachedLine(panel,shape)
							local dx,dy = (line[3]-line[1]),(line[4]-line[2])
							local mx,my = (line[3]+line[1])/2,(line[4]+line[2])/2
							local sign = pointInFill(panel,shape, {mx-dy*0.0001,my+dx*0.0001}) and 1 or -1
							local px,py = mx-dy*0.0001*sign,my+dx*0.0001*sign
							for st,stroke in ipairs(panel.f.strokes) do
								if stroke.group =="fill" and pointInBBox (px,py, stroke.bbox) then 
									stroke.index = st
									if pointInFill(panel,stroke,{px,py}) then 
										if startBox[5]>0 then 
											goto continue
										else
											goto done
										end
									end
								end
							end
							panel.fillsUsed[s] = true
							print("panel.fillsUsed[s]",s,panel.fillsUsed[s] )
			        -- local randomColor = {r=math.random()/4+0.6,g=math.random()/3+0.4,b=math.random()/3+0.4,a=0.5}
							table.insert(panel.f.strokes,{size=0.5, bbox=shape.bbox, ends=1, color=randomColor or lume.clone(drawingPanel.brush.color), group="fill", line=shape.line})
							editor.refreshStrokes()
							if  not (drawingPanel.fillAll or startBox[5]>0) then 
								break
							end
						end
						::continue::
					end
					::done::
					_time("fill fills")
				end

				-- -- SHOW TRIANGULATION
				if panel.debugView then 
					for s,stroke in lume.ripairs(panel.f.strokes) do
						if stroke.debug then
							table.remove(panel.f.strokes,s)
						end
					end
					editor.addTestLines(testpolys)
				end
      
			
				-- SHOW CONSTRAINED REGIONS
				print("fill complete")
			end
		else
			local fillable = editor.runFloodFill (startPoint.x,startPoint.y, panel.fillStack)
			print("fillable "..tostring(fillable))	
			if fillable then 
				filled = dataToCanvas(panel.filledData)
				print("prefilled")
				
				editor.paletteCheck(drawingPanel.brush.color)
				editor.drawBrushTexture (panel, filled)
				
				if panel.fillBehind then
					gfx.setColor (1, 1, 1, 1)
					gfx.setCanvas (filled)
					gfx.draw (panel.f.canvas)
					panel.f.canvas = filled
				end
				
				if not panel.fillBehind then
					if panel.l.format ~= "r8" then 
						gfx.setColor (drawingPanel.brush.color.r,drawingPanel.brush.color.g,drawingPanel.brush.color.b,1)
					end
					gfx.setCanvas (panel.f.canvas)
					if panel.expandFill then 
						gfx.setShader(shaders.expand)
						shaders.expandShader:send("image_size",{filled:getWidth(),filled:getHeight()})
					end
					gfx.setFilter(filled,panel.filter) 
					if panel.l.format == "r8" then 
						gfx.setShader(shaders.r8expand)
						shaders.r8expand:send("index",editor.paletteCheck(drawingPanel.brush.color)/255)
						shaders.r8expand:send("image_size",{filled:getWidth(),filled:getHeight()})
					end
					gfx.draw (filled)
					gfx.setShader()
				end
				-- local c = gfx.getCanvas()
				-- gfx.setCanvas()
				-- editor.clipFrame(panel.f)
				-- gfx.setCanvas(c)
			else
				editor.tempTip("Bad Region!")
			end
		end

	else
		if panel.format~= "vector" then 
			editor.tempTip("Out of bounds")
		end
	end

	if panel.fillTestData then 
		panel.fillTestData:release()
		panel.fillTestData=nil 
	end
	if panel.filledData then 
		panel.filledData:release()
		panel.filledData=nil 
	end
	if filled then 
		filled:release()
		filled=nil 
	end
	if panel.fillRef then 
		panel.fillRef:release()
		panel.fillRef=nil 
	end


	-- panel.canvasClicked = false
	
end



function editor.resetFill(panel,ref,point)
	panel.fillTestData= ref:newImageData()
	panel.filledData 	= limage.newImageData(panel.fillDims.w,panel.fillDims.h)
	local x,y =  point.x,point.y
	print("filling from: "..x.." "..y )
	if x<1 or y<1 or x>panel.fillTestData:getWidth()-1 or y>panel.fillTestData:getHeight()-1 then 
		return false 
	end
	local r,g,b,a 		= panel.fillTestData:getPixel(x,y )
	panel.expandFill=a == 0
	panel.fillStart 	= {r=r,g=g,b=b,a=a} 
	panel.fillStack  	= {}
	panel.fillGrid 		= {}
	if panel.l.format == "vector" and a > 0 then 
		return false 
	end
	for i = 1, mf(panel.fillDims.h) do
	    panel.fillGrid[i] = {}
	    for j = 1, mf(panel.fillDims.w) do
	        panel.fillGrid[i][j] = 0  
	    end
	end
	return true
end

local col = {}
local function test(x,y)
	if not( x>1 and y>1 and x<animationPanel.fillDims.w-1 and y<animationPanel.fillDims.h-1  ) then return false end
	if animationPanel.fillGrid[y][x]==0 then 
		col.r,col.g,col.b,col.a 	= animationPanel.fillTestData:getPixel( x, y )
		if animationPanel.fillStart.a==0 then 
			return col.a<=math.min(drawingPanel.fillTolerance,0.95)
		else 
			return colorCheck(col,animationPanel.fillStart,drawingPanel.fillTolerance,false)
		end
	else
		return false
	end
end

function colorCheck(a,b,n,o)
	local m = 1
	
	return (math.abs(a.r-b.r)*m<n and math.abs(a.g-b.g)*m<n and math.abs(a.b-b.b)*m<n)
end

local function paint(x,y)
	animationPanel.fillGrid[y][x]=1
	animationPanel.filledData:setPixel( x, y , 1,1,1,1)
		--drawingPanel.brush.color.r,drawingPanel.brush.color.g,drawingPanel.brush.color.b,1) 
end

function editor.runFloodFill(x, y, stack)
	stack  	= {}
	if test(x,y) then
		table.insert(stack,{y=y,x=x})
	end
	local w,h = animationPanel.fillTestData:getWidth(),animationPanel.fillTestData:getHeight()
	while #stack>0 do
		local p = stack[#stack]
		table.remove(stack,#stack)
		x, y = p.x, p.y
		if x == w or y == h or x ==2 or y ==2 then 
			return false 
		else
			if test(x,y) then
				local w, e = p.x, p.x
				while test(w,y) do w = w - 1 end
				while test(e,y) do e = e + 1 end
				for i = w,e,1 do
					if test(i,y) 	then 
						paint(i,y) 
						if test(i,y-1) 	then table.insert(stack,{y=y-1,x=i}) end
						if test(i,y+1) 	then table.insert(stack,{y=y+1,x=i}) end
					end
				end
			end
		end
	end
	return true
end
