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 min, max, sqrt, atan2 = math.min, math.max, math.sqrt, math.atan2
local mf = math.floor
local segments
local edges1 = {}
local edges2 = {}
local edgeN = 1
local grit = {}
local gritN = 1

local cutters = {}
local cutterN
local checks = {}
local checkN

local intersect
local max_points = 500000
local triangle_data = triangle_data or ffi.new("int[?]", 100000)
local out_data = out_data or ffi.new("float[?]", max_points)
-- local vertex_data   = vertex_data or ffi.new("float[?]",      200000)
-- local vertex_data2   = vertex_data2 or ffi.new("float[?]",      200000)
-- local dpoints = ffi.new("Point[50000]")
local dtris = ffi.new("Triangle[100000]")
local fillSetting = 0
local edje = ffi.new("Edge")

local function clusterSort(a, b)
	return a[1] < b[1]
end
local function sweepSort(a, b)
	return a[4] == b[4] and a[1] < b[1] or a[4] < b[4]
end
local function matchSort(a, b)
	if a[1] == b[1] then
		if a[2] == b[2] then
			if a[3] == b[3] then
				if a[4] == a[4] then
					return a[6] < b[6]
				else
					return a[4] < b[4]
				end
			else
				return a[3] < b[3]
			end
		else
			return a[2] < b[2]
		end
	else
		return a[1] < b[1]
	end
end

local classicConcat = classicConcat

local function inTable(t, v)
	for i = 1, #t, 1 do
		if t[i] == v then
			return true
		end
	end
	return false
end

local function addCutter(side, t, e, x, y1, y2)
	cutterN = cutterN + 1
	cutters[cutterN] = cutters[cutterN] or ffi.new("float[7]")
	cutters[cutterN][1], cutters[cutterN][2], cutters[cutterN][3], cutters[cutterN][4], cutters[cutterN][5], cutters[cutterN][6] =
		side, t, e, x, y1, y2
end

local function addCheck(x1, y1, x2, y2, t, e)
	checkN = checkN + 1
	checks[checkN] = checks[checkN] or ffi.new("float[7]")
	checks[checkN][1], checks[checkN][2], checks[checkN][3], checks[checkN][4], checks[checkN][5], checks[checkN][6] =
		x1, y1, x2, y2, t, e
end

local function clockwise(line)
	local sum = 0
	for l = 1, #line - 1, 1 do
		sum = sum + (line[l + 1].x - line[l].x) * (line[l + 1].y - line[l].y)
	end
	return sum > 0 and true or false, sum
end

local function triangleArea(a, b, c)
	local p = (a + b + c) * (a + b - c) * (a - b + c) * (-a + b + c)
	return sqrt(p) / 4
end

local function edgesame(p1, p2, p3, p4)
	return ((p1 == p3) and (p2 == p4)) or ((p1 == p4) and (p2 == p3))
end

local function sdist(x1, y1, x2, y2)
	return (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)
end
local function edgesNear(edge1, edge2)
	return
		(sdist(edge1.p1.x, edge1.p1.y, edge2.p1.x, edge2.p1.y) < 0.00001 and sdist(
		edge1.p2.x,
		edge1.p2.y,
		edge2.p2.x,
		edge2.p2.y
	) < 0.00001) or (sdist(edge1.p2.x, edge1.p2.y, edge2.p1.x, edge2.p1.y) < 0.00001 and sdist(
		edge1.p1.x,
		edge1.p1.y,
		edge2.p2.x,
		edge2.p2.y
	) < 0.00001)
	-- return  distPointToLineSegment2(edge1.p1.x, edge1.p1.y, edge2.p1.x, edge2.p1.y, edge2.p2.x, edge2.p2.y)<0.001
	--    and  distPointToLineSegment2(edge1.p2.x, edge1.p2.y, edge2.p1.x, edge2.p1.y, edge2.p2.x, edge2.p2.y)<0.001
end

local function segsNear(edge1, edge2)
	return distPointToLineSegment2(edge1[1], edge1[2], edge2[1], edge2[2], edge2[3], edge2[4]) < 0.0001
		and distPointToLineSegment2(edge1[3], edge1[4], edge2[1], edge2[2], edge2[3], edge2[4]) < 0.0001
end

local function segsSame(edge1, edge2)
	return (edge1[1] == edge2[1] and edge1[2] == edge2[2] and edge1[3] == edge2[3] and edge1[4] == edge2[4])
		or (edge1[1] == edge2[3] and edge1[2] == edge2[4] and edge1[3] == edge2[1] and edge1[4] == edge2[2])
end

local function segEdge(seg, edge)
	edje.p1.x, edje.p1.y, edje.p2.x, edje.p2.y = seg[1], seg[2], seg[3], seg[4]
	-- if not edje:same(edge) then
	--   if edgesNear(edje,edge)  then
	--     local d1 = math.dist(edje.p1.x, edje.p1.y, edge.p1.x, edge.p1.y)
	--     local d2 = math.dist(edje.p2.x, edje.p2.y, edge.p1.x, edge.p1.y)
	--     if d1<0.00001 then
	--       print(edje.p1.x, edje.p1.y, edge.p1.x, edge.p1.y)
	--       print(edje.p2.x, edje.p2.y, edge.p2.x, edge.p2.y)
	--       error("d1"..tostring(d1))
	--     elseif d2<0.00001 then
	--       print(edje.p2.x, edje.p2.y, edge.p1.x, edge.p1.y)
	--       print(edje.p1.x, edje.p1.y, edge.p2.x, edge.p2.y)
	--       error("d2"..tostring(d2))
	--     end
	--   end
	-- end
	return edje:same(edge)
end

local function segNearEdge(seg, edge)
	edje.p1.x, edje.p1.y, edje.p2.x, edje.p2.y = seg[1], seg[2], seg[3], seg[4]
	return edgesNear(edje, edge)
end

local function triTable(t)
	return t[0] < 0 and { t[1], t[2], t[3], t[4], t[5], t[6] } or { t[1], t[2] }
end

local function triTable2(p, f)
	return f[0] < 0 and { p.x, p.y, f[3], f[4], f[5], f[6] } or { p.x, p.y }
end

local function pointInDTri(tri, px, py, e)
	local e = e or 0
	return onSameSide2(px, py, tri.p1.x, tri.p1.y, tri.p2.x, tri.p2.y, tri.p3.x, tri.p3.y, e)
		and onSameSide2(px, py, tri.p2.x, tri.p2.y, tri.p1.x, tri.p1.y, tri.p3.x, tri.p3.y, e)
		and onSameSide2(px, py, tri.p3.x, tri.p3.y, tri.p1.x, tri.p1.y, tri.p2.x, tri.p2.y, e)
end

local function triTouchesGridSquare(tri, bbox)
	if
		pointInBox(tri.p1.x, tri.p1.y, bbox[1], bbox[2], bbox[3], bbox[4])
		or pointInBox(tri.p2.x, tri.p2.y, bbox[1], bbox[2], bbox[3], bbox[4])
		or pointInBox(tri.p2.x, tri.p2.y, bbox[1], bbox[2], bbox[3], bbox[4])
	then
		return true
	end

	local a, b, c = tri:getSidesLength()

	if a + b + c < bbox[5] then
		if
			bbox[1] <= max(tri.p1.x, tri.p2.x, tri.p3.x)
			and min(tri.p1.x, tri.p2.x, tri.p3.x) <= bbox[3]
			and bbox[2] <= max(tri.p1.y, tri.p2.y, tri.p3.y)
			and min(tri.p1.y, tri.p2.y, tri.p3.y) < bbox[4]
		then
			return true
		end
	else
		if
			intersect(tri.e1.p1.x, tri.e1.p1.y, tri.e1.p2.x, tri.e1.p2.y, bbox[1], bbox[2], bbox[1], bbox[4])
			or intersect(tri.e1.p1.x, tri.e1.p1.y, tri.e1.p2.x, tri.e1.p2.y, bbox[1], bbox[2], bbox[3], bbox[2])
			or intersect(tri.e1.p1.x, tri.e1.p1.y, tri.e1.p2.x, tri.e1.p2.y, bbox[3], bbox[4], bbox[1], bbox[4])
			or intersect(tri.e1.p1.x, tri.e1.p1.y, tri.e1.p2.x, tri.e1.p2.y, bbox[3], bbox[4], bbox[3], bbox[2])
			or intersect(tri.e1.p1.x, tri.e1.p1.y, tri.e1.p2.x, tri.e1.p2.y, bbox[1], bbox[2], bbox[1], bbox[4])
			or intersect(tri.e1.p1.x, tri.e1.p1.y, tri.e1.p2.x, tri.e1.p2.y, bbox[1], bbox[2], bbox[3], bbox[2])
			or intersect(tri.e1.p1.x, tri.e1.p1.y, tri.e1.p2.x, tri.e1.p2.y, bbox[3], bbox[4], bbox[1], bbox[4])
			or intersect(tri.e1.p1.x, tri.e1.p1.y, tri.e1.p2.x, tri.e1.p2.y, bbox[3], bbox[4], bbox[3], bbox[2])
			or intersect(tri.e1.p1.x, tri.e1.p1.y, tri.e1.p2.x, tri.e1.p2.y, bbox[1], bbox[2], bbox[1], bbox[4])
			or intersect(tri.e1.p1.x, tri.e1.p1.y, tri.e1.p2.x, tri.e1.p2.y, bbox[1], bbox[2], bbox[3], bbox[2])
			or intersect(tri.e1.p1.x, tri.e1.p1.y, tri.e1.p2.x, tri.e1.p2.y, bbox[3], bbox[4], bbox[1], bbox[4])
			or intersect(tri.e1.p1.x, tri.e1.p1.y, tri.e1.p2.x, tri.e1.p2.y, bbox[3], bbox[4], bbox[3], bbox[2])
		then
			return true
		end
	end

	return false
end

local function segTouchesGridSquare(seg, bbox)
	if
		pointInBox(seg[1], seg[2], bbox[1], bbox[2], bbox[3], bbox[4])
		or pointInBox(seg[3], seg[4], bbox[1], bbox[2], bbox[3], bbox[4])
	then
		return true
	end

	if math.dist(seg[1], seg[2], seg[3], seg[4]) < bbox[5] then
		if
			bbox[1] <= max(seg[1], seg[3])
			and bbox[3] >= min(seg[1], seg[3])
			and bbox[2] <= max(seg[2], seg[4])
			and bbox[4] >= min(seg[2], seg[4])
		then
			return true
		end
	else
		if
			intersect(seg[1], seg[2], seg[3], seg[4], bbox[1], bbox[2], bbox[1], bbox[4])
			or intersect(seg[1], seg[2], seg[3], seg[4], bbox[1], bbox[2], bbox[3], bbox[2])
			or intersect(seg[1], seg[2], seg[3], seg[4], bbox[3], bbox[4], bbox[1], bbox[4])
			or intersect(seg[1], seg[2], seg[3], seg[4], bbox[3], bbox[4], bbox[3], bbox[2])
		then
			return true
		end
	end

	return false
end

local function bruteConnect(triPoints, trit, tris)
	for t1 = 1, #tris, 1 do
		local tri1 = triPoints[t1]
		for t2 = 1, #tris, 1 do
			if t1 ~= t2 then
				local tri2 = triPoints[t2]
				local p1, p2, p3 = tri1[1] == tri2[1], tri1[1] == tri2[2], tri1[1] == tri2[3]
				if p1 or p2 or p3 then
					if p1 then
						if tri1[2] == tri2[2] then
							trit[t1][1] = t2
							trit[t2][1] = t1
						elseif tri1[2] == tri2[3] then
							trit[t1][1] = t2
							trit[t2][3] = t1
						elseif tri1[3] == tri2[2] then
							trit[t1][3] = t2
							trit[t2][1] = t1
						elseif tri1[3] == tri2[3] then
							trit[t1][3] = t2
							trit[t2][3] = t1
						end
					elseif p2 then
						if tri1[2] == tri2[1] then
							trit[t1][1] = t2
							trit[t2][1] = t1
						elseif tri1[2] == tri2[3] then
							trit[t1][1] = t2
							trit[t2][2] = t1
						elseif tri1[3] == tri2[1] then
							trit[t1][3] = t2
							trit[t2][1] = t1
						elseif tri1[3] == tri2[3] then
							trit[t1][3] = t2
							trit[t2][2] = t1
						end
					else
						if tri1[2] == tri2[2] then
							trit[t1][1] = t2
							trit[t2][2] = t1
						elseif tri1[2] == tri2[1] then
							trit[t1][1] = t2
							trit[t2][3] = t1
						elseif tri1[3] == tri2[2] then
							trit[t1][3] = t2
							trit[t2][2] = t1
						elseif tri1[3] == tri2[1] then
							trit[t1][3] = t2
							trit[t2][3] = t1
						end
					end
				else
					local p1, p2, p3 = tri1[2] == tri2[1], tri1[2] == tri2[2], tri1[2] == tri2[3]
					if p1 or p2 or p3 then
						if p1 then
							if tri1[3] == tri2[2] then
								trit[t1][2] = t2
								trit[t2][1] = t1
							elseif tri1[3] == tri2[3] then
								trit[t1][2] = t2
								trit[t2][3] = t1
							end
						elseif p2 then
							if tri1[3] == tri2[1] then
								trit[t1][2] = t2
								trit[t2][1] = t1
							elseif tri1[3] == tri2[3] then
								trit[t1][2] = t2
								trit[t2][2] = t1
							end
						else
							if tri1[3] == tri2[2] then
								trit[t1][2] = t2
								trit[t2][2] = t1
							elseif tri1[3] == tri2[1] then
								trit[t1][2] = t2
								trit[t2][3] = t1
							end
						end
					end
				end
			end
		end
	end
end

local function killTri(trit, tris, eos, t)
	for i = 1, 3, 1 do
		if trit[t][i] > 0 and not eos[t][i] then
			child = trit[t][i]
			trit[t][i] = 0
			table.insert(tris, t)
			killTri(trit, tris, eos, child)
		end
	end
end

local function canFill(tris, shape, filled, point)
	for i = 1, filled[0], 1 do
		if filled[i] == 0 and (not point or pointInDTri(tris[i], point.x, point.y)) then
			shape[0] = 1
			if shape[1] == i then
				print("breaking loop early, this shouldnt be happening")
				return false
			end
			shape[1] = i
			return true
		end
	end
	return false
end

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

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

local function pointSort(a, b)
	return a.x < b.x --dist(0,0,a.x,a.y)<dist(0,0,b.x,b.y)
end

local function findLoop(out, shape, edges, used)
	-- table.insert(out.paths,shape)
	local x, y = shape[#shape - 1], shape[#shape]
	local addedSeg = false
	for e = #edges, 1, -1 do
		if not used[e] then
			local isSeg = edges[e][5] ~= 1
			if not used.prevSeg and not isSeg and addedSeg then
				break
			end
			local x1, y1, x2, y2 = edges[e][1], edges[e][2], edges[e][3], edges[e][4]
			local left, right = (x == x1 and y == y1), (x == x2 and y == y2)
			local ix, iy = left and x2 or x1, left and y2 or y1
			if left or right then
				if #shape > 3 then
					for s = 3, #shape - 1, 2 do
						if ix == shape[s] and iy == shape[s + 1] then
							goto continue
						end
					end
				end
				if ix == shape[1] and iy == shape[2] then
					local used2 = lume.clone(used)
					used2[e] = true
					-- concat(shape,{ix,iy})
					local area = polyArea(shape)
					if not out.area or area < out.area then
						table.remove(shape)
						table.remove(shape)
						table.remove(shape)
						table.remove(shape)
						out.area = area
						out.shape = shape
						out.used = used2
					end
				else
					local used2 = lume.clone(used)
					used2[e] = true
					if isSeg then
						addedSeg = true
						used2.segged = true
					end
					used2.prevSeg = isSeg
					findLoop(out, lume.concat(shape, { ix, iy }), edges, used2)
				end
			end
		end
		::continue::
	end
end

-- local function matchCurves(tris, trit, segments, used, testpolys)
-- 	cutterN = 0
-- 	checkN = 0
-- 	local addCutters = testpolys == 1

-- 	for t = 1, #tris, 1 do
-- 		local tri = tris[t]
-- 		addCheck(
-- 			min(tri.e1.p1.x, tri.e1.p2.x),
-- 			min(tri.e1.p1.y, tri.e1.p2.y),
-- 			max(tri.e1.p1.x, tri.e1.p2.x),
-- 			max(tri.e1.p1.y, tri.e1.p2.y),
-- 			t,
-- 			1
-- 		)
-- 		addCheck(
-- 			min(tri.e2.p1.x, tri.e2.p2.x),
-- 			min(tri.e2.p1.y, tri.e2.p2.y),
-- 			max(tri.e2.p1.x, tri.e2.p2.x),
-- 			max(tri.e2.p1.y, tri.e2.p2.y),
-- 			t,
-- 			2
-- 		)
-- 		addCheck(
-- 			min(tri.e3.p1.x, tri.e3.p2.x),
-- 			min(tri.e3.p1.y, tri.e3.p2.y),
-- 			max(tri.e3.p1.x, tri.e3.p2.x),
-- 			max(tri.e3.p1.y, tri.e3.p2.y),
-- 			t,
-- 			3
-- 		)
-- 	end

-- 	local m = 0
-- 	for s = 1, segments[0][0], 1 do
-- 		local seg = segments[s]
-- 		addCheck(min(seg[1], seg[3]), min(seg[2], seg[4]), max(seg[1], seg[3]), max(seg[2], seg[4]), s, 0)
-- 	end

-- 	clearTail(checks, checkN + 1)
-- 	table.sort(checks, matchSort)

-- 	_time("prep checks ")

-- 	-- match edges to segs and add unmatched to open table
-- 	local shared = 0
-- 	local ceg
-- 	for c = 1, #checks, 1 do
-- 		local check = checks[c]
-- 		if check[6] == 0 then
-- 			ceg = check
-- 		else
-- 			if ceg then
-- 				if check[1] == ceg[1] and check[2] == ceg[2] and check[3] == ceg[3] and check[4] == ceg[4] then
-- 					local seg = segments[ceg[5]]
-- 					local t, e = check[5], check[6]
-- 					local tri = tris[t]
-- 					local edge = e == 1 and tri.e1 or (e == 2 and tri.e2 or e == 3 and tri.e3)
-- 					local a, b = e == 1 and 1 or (e == 2 and 2 or 3), e == 1 and 2 or (e == 2 and 3 or 1)
-- 					-- table.insert(testpolys,{seg[1],seg[2],seg[3],seg[4]})
-- 					if segEdge(seg, edge) then
-- 						if ceg[5] > 0 and ceg[5] < segments[0][0] then
-- 							used[ceg[5]] = 1
-- 						end
-- 						trit[t][e] = 0
-- 					end
-- 				else
-- 					local p = checks[c - 1]
-- 					if
-- 						p
-- 						and p[6] ~= 0
-- 						and check[1] == p[1]
-- 						and check[2] == p[2]
-- 						and check[3] == p[3]
-- 						and check[4] == p[4]
-- 					then
-- 						local t1, e1 = check[5], check[6]
-- 						local t2, e2 = p[5], p[6]
-- 						local tri1, tri2 = tris[t1], tris[t2]
-- 						local edge1 = e1 == 1 and tri1.e1 or (e1 == 2 and tri1.e2 or e1 == 3 and tri1.e3)
-- 						local edge2 = e2 == 1 and tri2.e1 or (e2 == 2 and tri2.e2 or e2 == 3 and tri2.e3)
-- 						if edge1:same(edge2) then
-- 							trit[t1][e1] = t2
-- 							trit[t2][e2] = t1

-- 							if addCutters then
-- 								addCutter(
-- 									0,
-- 									t1,
-- 									e1,
-- 									min(edge1.p1.x, edge1.p2.x),
-- 									min(edge1.p1.y, edge1.p2.y),
-- 									max(edge1.p1.y, edge1.p2.y)
-- 								)
-- 								addCutter(1, t1, e1, max(edge1.p1.x, edge1.p2.x), 0, 0)

-- 								addCutter(
-- 									0,
-- 									t2,
-- 									e2,
-- 									min(edge2.p1.x, edge2.p2.x),
-- 									min(edge2.p1.y, edge2.p2.y),
-- 									max(edge2.p1.y, edge2.p2.y)
-- 								)
-- 								addCutter(1, t2, e2, max(edge2.p1.x, edge2.p2.x), 0, 0)
-- 							end
-- 						end
-- 					end
-- 				end
-- 			end
-- 		end
-- 	end

-- 	-- brute force cleanup
-- 	for s = 1, segments[0][0], 1 do
-- 		if used[s] < 1 then
-- 			local seg = segments[s]
-- 			for t = 1, #tris, 1 do
-- 				local tri = tris[t]
-- 				for e = 1, 3, 1 do
-- 					local edge = e == 1 and tri.e1 or (e == 2 and tri.e2 or e == 3 and tri.e3)
-- 					local a, b = e == 1 and 1 or (e == 2 and 2 or 3), e == 1 and 2 or (e == 2 and 3 or 1)
-- 					if segNearEdge(seg, edge) then
-- 						used[s] = 1
-- 						trit[t][e] = 0
-- 					end
-- 				end
-- 			end
-- 		end
-- 	end

-- 	-- DEBUG
-- 	-- if type(testpolys)=="table" then
-- 	--   for s=1,segments[0][0], 1 do
-- 	--     if used[s]<1 then
-- 	--       local seg = segments[s]
-- 	--       table.insert(testpolys,{color={r=1,b=0,g=0},seg[1],seg[2],seg[3],seg[4]})
-- 	--     end
-- 	--   end
-- 	-- end

-- 	fixOpenEdges(tris, trit, addCutters)
-- end

-- local function fixOpenEdges(tris, trit, addCutters)
-- 	local tofix = {}
-- 	for t = 1, #tris, 1 do
-- 		for e = 1, 3, 1 do
-- 			if trit[t][e] == -1 then
-- 				local a, b =
-- 					e == 1 and tris[t].p1 or (e == 2 and tris[t].p2 or tris[t].p3),
-- 					e == 1 and tris[t].p2 or (e == 2 and tris[t].p3 or tris[t].p1)
-- 				table.insert(tofix, { a.x, a.y, b.x, b.y, t, e })
-- 			end
-- 		end
-- 	end

-- 	for f1, fix1 in ipairs(tofix) do
-- 		for f2, fix2 in ipairs(tofix) do
-- 			if f1 ~= f2 then
-- 				if segsSame(fix1, fix2) then
-- 					trit[fix1[5]][fix1[6]] = fix2[5]
-- 					trit[fix2[5]][fix2[6]] = fix1[5]
-- 					if addCutters then
-- 						addCutter(
-- 							0,
-- 							fix1[5],
-- 							fix1[6],
-- 							min(fix1[1], fix1[3]),
-- 							min(fix1[2], fix1[4]),
-- 							max(fix1[2], fix1[4])
-- 						)
-- 						addCutter(1, fix2[5], fix2[6], max(fix2[1], fix2[3]), 0, 0)
-- 					end
-- 				end
-- 			end
-- 		end
-- 	end
-- end

function flood_fillVector(panel, segmentBuf, bbox, force, startPoint, strokes)
	segments = segmentBuf
	intersect = _G.intersect
	_start_timer("fill vector")
	print("start vector fill---------------------")
	_time("", false)
	collectgarbage("stop")
	cutterN = 0
	local divs = animatePanel.fillDivs or 1
	local testpolys = {}

	local segmentsUsed = ffi.new("unsigned int[?]", segments[0][0] + 1)

	local segmentN = segments[0][0]

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

	bbox.tw = bbox.w / divs
	bbox.th = bbox.h / divs

	local start, grid
	local es = { "e1", "e2", "e3" }

	_time("prep")

	local tris = {}
	local trit

	local found = false
	local numpoints = 0
	local numsegs = segmentN
	local point_data = ffi.new("float[?]", segmentN * 4)
	local segment_data = ffi.new("int[?]", segmentN * 2)
	-- make this faster
	for s = 1, segmentN, 1 do
		local e = s - 1

		found = false
		for p = 0, numpoints, 1 do
			if segments[s][1] == point_data[p * 2] and segments[s][2] == point_data[p * 2 + 1] then
				segment_data[e * 2] = p
				found = true
				break
			end
		end
		if not found then
			segment_data[e * 2] = numpoints
			point_data[numpoints * 2], point_data[numpoints * 2 + 1] = segments[s][1], segments[s][2]
			numpoints = numpoints + 1
		end

		found = false
		for p = 0, numpoints, 1 do
			if segments[s][3] == point_data[p * 2] and segments[s][4] == point_data[p * 2 + 1] then
				segment_data[e * 2 + 1] = p
				found = true
				break
			end
		end
		if not found then
			segment_data[e * 2 + 1] = numpoints
			point_data[numpoints * 2], point_data[numpoints * 2 + 1] = segments[s][3], segments[s][4]
			numpoints = numpoints + 1
		end
	end

	if not force then
		panel.prevFillPoints = panel.prevFillPoints or ffi.new("float[1]")
		panel.prevNumFillPoints = panel.prevNumFillPoints or 1
		if panel.prevNumFillPoints == numpoints and panel.prevFillGap == panel.fillGap then
			local changed = false
			for p = 1, numpoints * 2, 1 do
				if panel.prevFillPoints[p] ~= point_data[p] then
					changed = true
					break
				end
			end
			if changed then
				if numpoints > panel.prevNumFillPoints then
					panel.prevFillPoints = ffi.new("float[?]", segmentN * 4)
					ffi_copy(panel.prevFillPoints, point_data)
				end
				panel.prevNumFillPoints = numpoints
			else
				print("DONT FILL, NO CHANGE")
				return panel.lastFill
			end
		end
	end

	_time(string.format("reduce set - points:%i segs:%i", numpoints, numsegs))

	if numpoints < 3 then
		return {}
	end
	local counts = ffi.new("int[10]")
	if true then
		local holes, numholes = ffi.new("float[1000]"), 0

		if strokes then
			if strokes.line then -- splitting a single fill shape
				numholes = #strokes.line - 1
				for i = 0, numholes - 1, 1 do
					holes[i * 2], holes[i * 2 + 1] = strokes.line[i + 2][0][7], strokes.line[i + 2][0][8]
				end
				counts[4] = 1
			else -- exclude interiors of existing fill shapes
				for i = 0, numholes - 1, 1 do
					if strokes[i + 1].point then
						holes[i * 2], holes[i * 2 + 1] = strokes[i + 1].point[1], strokes[i + 1].point[2]
						numholes = numholes + 1
					end
				end
				counts[4] = 1
			end
		end

		numtris = ffi.C.ctriangulate(
			point_data,
			segment_data,
			holes,
			triangle_data,
			out_data,
			counts,
			ffi.new("triangle_params", numpoints, numsegs, numholes, false)
		)
		_time("triangulate_fast " .. tostring(numtris))
		trit = ffi.new(string.format("int[%i][4]", numtris + 3), { { -1 } })
		for t = 0, numtris - 1, 1 do
			local tri = dtris[t]
			tri.p1.x, tri.p1.y, tri.p1.id, tri.p2.x, tri.p2.y, tri.p2.id, tri.p3.x, tri.p3.y, tri.p3.id =
				out_data[t * 6],
				out_data[t * 6 + 1],
				t * 3 + 1,
				out_data[t * 6 + 2],
				out_data[t * 6 + 3],
				t * 3 + 2,
				out_data[t * 6 + 4],
				out_data[t * 6 + 5],
				t * 3 + 3
        
			tri.e1.p1.x,tri.e1.p1.y, tri.e1.p2.x,tri.e1.p2.y = tri.p1.x,tri.p1.y, tri.p2.x,tri.p2.y
			tri.e2.p1.x,tri.e2.p1.y, tri.e2.p2.x,tri.e2.p2.y = tri.p2.x,tri.p2.y, tri.p3.x,tri.p3.y
			tri.e3.p1.x,tri.e3.p1.y, tri.e3.p2.x,tri.e3.p2.y = tri.p3.x,tri.p3.y, tri.p1.x,tri.p1.y

			trit[t + 1][1] = triangle_data[t * 3 + 2]
			trit[t + 1][2] = triangle_data[t * 3]
			trit[t + 1][3] = triangle_data[t * 3 + 1]
			table.insert(tris, tri)
			if panel.debugView then
				table.insert(
					testpolys,
					{
						ox = -bbox.w * 2,
						oy = panel.debugSelectionOffset or bbox.h,
						color = { r = 0, g = 1, b = 0, a = 1 },
						tri.p1.x,
						tri.p1.y,
						tri.p2.x,
						tri.p2.y,
						tri.p3.x,
						tri.p3.y,
						tri.p1.x,
						tri.p1.y,
					}
				)
			end
		end

		local numsides = #tris * 3 + segmentN
		local sides = ffi.new("side[?]", numsides)

		print("numsides", numsides)
		local te = 0
		for t = 1, #tris, 1 do
			local tri = tris[t]
			for e = 1, 3, 1 do
				sides[te].edge = e == 1 and tri.e1 or (e == 2 and tri.e2 or tri.e3)
				sides[te].lx = math.min(sides[te].edge.p1.x, sides[te].edge.p2.x)
				sides[te].rx = math.max(sides[te].edge.p1.x, sides[te].edge.p2.x)
				sides[te].ly = sides[te].lx == sides[te].edge.p1.x and sides[te].edge.p1.y or sides[te].edge.p2.y
				sides[te].ry = sides[te].rx == sides[te].edge.p1.x and sides[te].edge.p1.y or sides[te].edge.p2.y
				sides[te].t = t
				sides[te].e = e
				te = te + 1
			end
		end

		segments[0][14] = -999999999

		for s = 1, segmentN, 1 do
			local seg = segments[s]
			sides[te].edge.p1.x, sides[te].edge.p1.y = seg[1], seg[2]
			sides[te].edge.p2.x, sides[te].edge.p2.y = seg[3], seg[4]
			sides[te].lx = math.min(sides[te].edge.p1.x, sides[te].edge.p2.x)
			sides[te].rx = math.max(sides[te].edge.p1.x, sides[te].edge.p2.x)
			sides[te].ly = sides[te].lx == sides[te].edge.p1.x and sides[te].edge.p1.y or sides[te].edge.p2.y
			sides[te].ry = sides[te].rx == sides[te].edge.p1.x and sides[te].edge.p1.y or sides[te].edge.p2.y
			sides[te].t = s
			sides[te].e = 0
			te = te + 1
		end

		_time("presort")

		ffi.C.qsort(sides, numsides, ffi.sizeof("side"), ffi.C.compSides)

		_time("sort")

		local segside, segment
		local start

		-- print("numsides-----------------------------------------------",numsides)
		-- if strokes then
		--   print(" fill existing shapes")
		--   local search = numsides/3
		--   for i=0,numsides-1,1 do
		--     local side = sides[i]
		--     for j=math.max(0,i-search),math.min(numsides-1,i+search),1 do
		--       local zide = sides[j]
		--       if i~=j and side.edge:same(zide.edge)
		--               and not ( (side.e>0 and zide.e>0) or (side.e==0 and zide.e==0)) then
		--         if side.e>0 then trit[side.t][side.e] = 0 end
		--         if zide.e>0 then trit[zide.t][zide.e] = 0 end
		--       end
		--     end
		--   end
		-- else
		print(" finding new fill shapes")
		start = 0
		for i = 0, numsides - 1, 1 do
			local side = sides[i]
			if side.e == 0 then
				start = i
				segside = side
				segment = segments[side.t]
			else
				if segside and segside.edge:same(side.edge) then
					trit[side.t][side.e] = 0
				end
			end
		end
		-- end
		_time("create dpoints/dtris etc ")
	end

	panel.fillsUsed = {}
	panel.cachedStrokes = {}
	panel.cachedFills = {}

	panel.prevFillGap = panel.fillGap

	-- if panel.debugView and panel.debugTris then
	--   for t,tri in ipairs(tris) do
	--     table.insert(testpolys,{color={r=0,g=1,b=0,a=1},tri.p1.x,tri.p1.y,tri.p2.x,tri.p2.y,tri.p3.x,tri.p3.y,tri.p1.x,tri.p1.y})
	--   end
	-- end

	_time("rematch curves")

	local shapes = flood_groupTriangles(panel, startPoint, segments, trit, tris, testpolys, bbox)

	print("testpolys out", #testpolys)
	-- if true then return {testpolys=testpolys} end

	print("fill shapes: " .. tostring(#shapes))

	_end_timer("fill vector")
	print("end vector fill---------------------")

	return {
		segments = segments,
		trit = trit,
		tris = tris,
		shapes = shapes,
		testpolys = testpolys,
	}
end

function flood_groupTriangles(panel, startPoint, segments, trit, tris, testpolys, bbox)
	if startPoint == true then
		return {}
	end
	_start_timer("group triangles")
	-- if not startPoint then _disable_timers() end
	print("grouping triangles...")
	local segments = segments or panel.lastFill.segments
	local trit = trit or panel.lastFill.trit
	local tris = tris or panel.lastFill.tris
	local testpolys = testpolys or panel.lastFill.testpolys
	local debugw = bbox and bbox.w or 0
	local debugh = panel.debugSelectionOffset or bbox and bbox.h or 0
	-- group triangles into fill shapes
	local filled = ffi.new("uint16_t[?]", #tris + 1)
	filled[0] = #tris
	local shapes = {}
	local visited = {}
	local maxCheckedTris = 0
	local shape = {}
	local tried = {}
	local red = { r = 1, g = 0, b = 0 }
	local gridw, gridh = 32, 32
	local sgrid = ffi.new(string.format("u16[%i][%i][%i]", gridw, gridh, 512))
	local numLoose = 1
	local numClusters = 0
	while true do
		::continue::
		_tick("cluster")
		if not canFill(tris, shape, filled, startPoint) then
			break
		end
		local somePointInShape
		local color = panel.debugView and { r = math.random(), g = math.random(), b = math.random() } or red
		local added = {}
		local segs = {}
		local temppolys = {}
		local valid = true
		local grow = true
		local testpoly = {}
		local r = 1 + mf(math.random() * 9)

		while grow do
			grow = false
			local checkedTris = 0
			for s = 1, shape[0], 1 do
				if not added[shape[s]] then
					checkedTris = checkedTris + 1
					local tri = tris[shape[s]]
					if not somePointInShape then
						somePointInShape = {
							(tri.p1.x + tri.p2.x + tri.p3.x) / 3,
							(tri.p1.y + tri.p2.y + tri.p3.y) / 3,
						}
						-- print("somePointInShape",somePointInShape)
					end
					if panel.debugView and not startPoint then
						table.insert(
							temppolys,
							{ tri.p1.x, tri.p1.y, tri.p2.x, tri.p2.y, tri.p3.x, tri.p3.y, tri.p1.x, tri.p1.y }
						)
					end

					filled[shape[s]] = 1

					for e = 1, 3, 1 do
						local connectedTriangleIndex = trit[shape[s]][e]
						local seg = e == 1 and { tri.p1, tri.p2 } or (
								e == 2 and { tri.p2, tri.p3 } or { tri.p3, tri.p1 }
							)
						seg[3] = math.min(seg[1].x, seg[2].x)
						seg[4] = math.min(seg[1].y, seg[2].y)
						seg[5] = math.max(seg[1].x, seg[2].x)
						seg[6] = math.max(seg[1].y, seg[2].y)
						seg[7] = dist(seg[1].x, seg[1].y, seg[2].x, seg[2].y)
						-- tried[shape[s]] = true

						-- shape edges
						if connectedTriangleIndex == 0 then --and not tried[shape[s]] then
							-- if tri[e][0]==-2 then end
							table.insert(segs, seg)
						else
							if connectedTriangleIndex < 0 then
								seg[7] = -seg[7] -- keep track of dead outer edges
							end

							table.insert(segs, seg)

							if connectedTriangleIndex <= #tris then
								if not visited[connectedTriangleIndex] then
									visited[connectedTriangleIndex] = true

									if connectedTriangleIndex > 0 then
										if not added[connectedTriangleIndex] then
											-- gap closing
											local d1, d2, d3 =
												dist(tri.p1.x, tri.p1.y, tri.p2.x, tri.p2.y),
												dist(tri.p2.x, tri.p2.y, tri.p3.x, tri.p3.y),
												dist(tri.p1.x, tri.p1.y, tri.p3.x, tri.p3.y)
											local len = e == 1 and d1 or (e == 2 and d2 or d3)
											local stopGap = false
											if panel.fillGap > 0 and len < panel.fillGap * 2 then
												for e = 1, 3, 1 do
													local index = trit[connectedTriangleIndex][e] --nextTriangleIndex
													if
														index ~= connectedTriangleIndex
														and index ~= s
														and index > 0
														and index <= #tris
													then
														local tri = tris[index]
														local dd1, dd2, dd3 =
															dist(tri.p1.x, tri.p1.y, tri.p2.x, tri.p2.y),
															dist(tri.p2.x, tri.p2.y, tri.p3.x, tri.p3.y),
															dist(tri.p1.x, tri.p1.y, tri.p3.x, tri.p3.y)
														-- search forward to see if the triangles get bigger later
														if
															(dd1 + dd2 + dd3) > (d1 + d2 + d3)
															and triangleArea(dd1, dd2, dd3) > triangleArea(d1, d2, d3)
														then
															--or (max4(0,dd1,dd2,dd3)>len) then
															stopGap = true
														end
													end
												end
											end

											if not stopGap then
												grow = true
												shape[0] = shape[0] + 1
												shape[shape[0]] = connectedTriangleIndex
											end
										end
									end
								end
							end
							added[shape[s]] = true
						end
					end
				end
			end
			maxCheckedTris = max(maxCheckedTris, checkedTris)
		end

		::kill::
		_tock("cluster")
		-- if not (valid and #segs>=700 and #segs<800) then goto continue end
		if not (valid and #segs >= 3) then
			goto continue
		end
		-- if not (valid and #segs>500) then goto continue end

		-------------------------------------------------
		-- PUT EVERYTHING FROM HERE ON INTO SOME FUNCTION
		-------------------------------------------------

		--    for s=1,#segs,1 do
		--   local seg =segs[s]
		--   table.insert(testpolys,{color=color,seg[1].x,seg[1].y,seg[2].x,seg[2].y})
		-- end

		-- if duplicate, remove both
		local rems = 0
		_tick("dupes")
		table.sort(segs, function(a, b)
			if a[3] == b[3] then
				return a[4] < b[4]
			else
				return a[3] < b[3]
			end
		end)

		for s1 = #segs, 1, -1 do
			if segs[s1] then
				for s2 = s1 + 1, math.max(1, s1 - 64), -1 do
					if s1 ~= s2 and segs[s2] then
						if
							(segs[s1][3] == segs[s2][3])
							and (segs[s1][4] == segs[s2][4])
							and (segs[s1][5] == segs[s2][5])
							and (segs[s1][6] == segs[s2][6])
						then
							table.remove(segs, max(s1, s2))
							table.remove(segs, min(s1, s2))
							rems = rems + 1
							break
						end
					end
				end
			end
		end
		_tock("dupes")

		-- local rems = 0
		-- for s1=#segs,1,-1 do
		--   if segs[s1] then
		--     local a1,b1 = segs[s1][1],segs[s1][2]
		--     for s2=#segs,1,-1 do
		--       local a2,b2 = segs[s2][1],segs[s2][2]
		--       if s1~=s2 and ((a1 == a2 and b1 == b2) or (a1 == b2 and b1 == a2)) then
		--         table.remove(segs,max(s1,s2))
		--         table.remove(segs,min(s1,s2))
		--         rems = rems+1
		--         break
		--       end
		--     end
		--   end
		-- end
		-- _time(string.format("remove dupes %i",rems))

		-- remove outer shapes
		_tick("outers")
		local psum, nsum = 0, 0
		for s = 1, #segs, 1 do
			if not segs[s][7] then
				print("segs[s][5]", inspect(segs[s]))
			else
				-- if segs[s][3]<0 then
				-- table.insert(testpolys,{color=color,segs[s][1].x,segs[s][1].y,segs[s][2].x,segs[s][2].y})
				-- end
				psum = psum + (segs[s][7] > 0 and segs[s][7] or 0)
				nsum = nsum - (segs[s][7] < 0 and segs[s][7] or 0)
				table.remove(segs[s], 7)
				table.remove(segs[s], 6)
				table.remove(segs[s], 5)
				table.remove(segs[s], 4)
				table.remove(segs[s], 3)
			end
		end
		_tock("outers")

		if panel.debugView then
			for s = 1, #segs, 1 do
				local seg = segs[s]
				fill_addTestLines({
					{ color = color, ox = 0, oy = debugh, seg[1].x, seg[1].y, seg[2].x, seg[2].y },
				})
			end
		end

		-- print("psum/nsum",psum,nsum,psum/nsum)

		if nsum > 0 then
			if psum / nsum < 8 then
				goto continue
			end
		end

		--remove trailing segs
		_tick("trails")
		-- for s1=#segs,1,-1 do
		--   if segs[s1] then
		--     local a1,b1 = segs[s1][1],segs[s1][2]
		--     local trailing = 0
		--     for ss=1,2,1 do
		--       local side = ss==1 and a1 or b1
		--       for s2=1,#segs,1 do
		--         local a2,b2 = segs[s2][1],segs[s2][2]
		--         if s1~=s2 and ((side==a2) or (side==b2)) then
		--           trailing=trailing+1
		--           break
		--         end
		--       end
		--     end

		--     if trailing<2 then
		--       print("trail removed")
		--       table.remove(segs,s1)
		--     end
		--   end
		-- end
		_tock("trails")

		if not segs[1] then
			goto continue
		end

		numClusters = numClusters + 1
		if panel.debugView then
			for t = 1, #temppolys, 1 do
				temppolys[t].color = temppolys[t].color or color
				temppolys[t].ox = -debugw
				temppolys[t].oy = debugh
				-- temppolys[t].oy = numClusters*200
				table.insert(testpolys, temppolys[t])
			end
		end

		_tick("grid")
		local bbox = { segs[1][1].x, segs[1][1].y, segs[1][1].x, segs[1][1].y }
		for s = 1, #segs, 1 do
			local seg = segs[s]
			bbox[1] = math.min(bbox[1], seg[1].x, seg[2].x)
			bbox[2] = math.min(bbox[2], seg[1].y, seg[2].y)
			bbox[3] = math.max(bbox[3], seg[1].x, seg[2].x)
			bbox[4] = math.max(bbox[4], seg[1].y, seg[2].y)
		end
		local bbox = { bbox[1] - 1, bbox[2] - 1, bbox[3] + 1, bbox[4] + 1 }
		local tw, th = (bbox[3] - bbox[1]) / gridw, (bbox[4] - bbox[2]) / gridh

		for s = 1, #segs, 1 do
			local seg = segs[s]
			local x1, y1 = math.floor((seg[1].x - bbox[1]) / tw), math.floor((seg[1].y - bbox[2]) / th)
			sgrid[y1][x1][0] = sgrid[y1][x1][0] + 1
			sgrid[y1][x1][sgrid[y1][x1][0]] = s
			local x2, y2 = math.floor((seg[2].x - bbox[1]) / tw), math.floor((seg[2].y - bbox[2]) / th)
			if not (x1 == x2 and y1 == y2) then
				sgrid[y2][x2][0] = sgrid[y2][x2][0] + 1
				sgrid[y2][x2][sgrid[y2][x2][0]] = s
			end
		end
		_tock("grid")

		-- join into outline
		local outlines = {}
		local loosies = {}
		local used = {}
		local lineCount = 0
		local usedCount = 0
		_tick("join")
		while usedCount < #segs do
			::newline::
			lineCount = lineCount + 1
			-- print("lineCount",lineCount)
			-- print("usedCount",usedCount,"/",#segs)
			local start
			for s = 1, #segs, 1 do -- find a start seg
				if not used[s] then
					start = s
					break
				end
			end
			if not start then
				goto done
			end
			local outline = { segs[start][1], segs[start][2] }
			used[start] = lineCount
			local flipped = false
			local prevSeg = 0
			while true do
				::restart::
				local added, ss, sa, sb, side = 0

				local n, m = (flipped and 1 or #outline), (flipped and 2 or #outline - 1)
				local p = #outline > 1 and outline[m] or outline[n]

				local gx, gy = mf((outline[n].x - bbox[1]) / tw), mf((outline[n].y - bbox[2]) / th)
				local tile1 = sgrid[gy][gx]
				gx, gy = mf((outline[m].x - bbox[1]) / tw), mf((outline[m].y - bbox[2]) / th)
				local tile2 = sgrid[gy][gx]

				-- print("tile",tile[0])
				-- for s=1,#segs,1 do
				local sused = {}
				for i = 1, tile1[0] + tile2[0], 1 do
					local s = i > tile1[0] and tile2[i - tile1[0]] or tile1[i]
					if not sused[s] and segs[s] then
						sused[s] = true
						local seg = segs[s]
						local a, b = seg[1], seg[2]
						if used[s] ~= lineCount then
							local matched = outline[n] == a and 1 or ((outline[n] == b) and 0 or -1)
							local c = (matched == 1 and b or a)
							-- print(outline[n],a,b,matched)
							-- print(matched>-1,matched,p~=c)
							if matched > -1 and p ~= c then
								-- print("seg matched",s,matched)
								ss, sa, sb, side = s, a, b, matched
								added = added + 1
								if added > 1 or #outline == 1 then
									break
								end
							end
						end
					end
				end

				-- print("added",added)
				if added == 1 then -- if we only found one, add it
					local sc = side == 0 and sa or sb
					table.insert(outline, flipped and 1 or #outline + 1, ffi.new("Point",sc.x, sc.y))
					used[ss] = lineCount
					prevSeg = ss
					usedCount = usedCount + 1
				elseif added > 0 then
					used[prevSeg] = 0
					-- if a branch is found, start looking from the other end of the line
					if not flipped then
						-- print("flip at",added)
						flipped = true
						added = 0
						goto restart

					-- otherwise just exit the line if we already searched the other end
					else
						table.insert(loosies, outline)
						-- print("add loosie",#outline)
						break
					end
				else -- if no link is found then
					-- if it loops, save it
					if outline[#outline] == outline[1] then
						table.insert(outlines, outline)
						-- print("add outline")
					end
					if usedCount >= #segs then
						goto done
					else
						goto newline
					end
				end
			end
		end

		::done::
		_tock("join")

		-- DEBUG

		-- stash testshape loops that are already closed
		for l = #loosies, 1, -1 do
			local loosie = loosies[l]
			-- local testshape = {}
			-- for p,pt in ipairs(loosie) do
			--   table.insert(testshape,pt.x)
			--   table.insert(testshape,pt.y)
			-- end
			-- table.insert(testpolys,testshape)
			if loosie[1] == loosie[#loosie] then
				local dupe
				-- for o,outline in ipairs(outlines)do
				--   if #outline==loosie and outline[1]==loosie[1] and outline[2]==loosie[2] then
				--     dupe = true
				--     print("\n DOOOOP \n")
				--   end
				-- end
				-- if not dupe then
				table.insert(outlines, loosie)
				-- end
				table.remove(loosies, l)
			else
			end
		end

		if panel.debugView then
			for o, outline in ipairs(loosies) do
				local testshape = {}
				for p, pt in ipairs(outline) do
					table.insert(testshape, pt.x)
					table.insert(testshape, pt.y)
				end
				testshape.color = { r = 1, g = 0, b = 0 }
				testshape.ox = debugw
				testshape.oy = debugh + math.random() * 10
				table.insert(testpolys, testshape)
				numLoose = numLoose + 1
			end

			for o, outline in ipairs(outlines) do
				local testshape = {}
				for p, pt in ipairs(outline) do
					table.insert(testshape, pt.x)
					table.insert(testshape, pt.y)
				end

				testshape.color = { r = 0, g = 1, b = 0 }
				testshape.ox = debugw
				testshape.oy = debugh + math.random() * 10
				table.insert(testpolys, testshape)
				numLoose = numLoose + 1
			end
		end

		-- DEBUG
		-- if #loosies>1  then
		--   for o,outline in ipairs(loosies) do
		--     local testshape = {}
		--     for p,pt in ipairs(outline) do
		--       table.insert(testshape,pt.x)
		--       table.insert(testshape,pt.y)
		--     end
		--     -- testshape.color=clockwise(outline) and {r=1,g=0,b=0} or {r=0,g=0,b=1}
		--     table.insert(testpolys,testshape)
		--   end
		-- end

		-- stitch the remaining ones together
		_tick("stitch")
		local offset = 0
		local current = 1
		local nada = true
		while #loosies > 1 do
			-- print("check loossIE")
			current = wrap2(current, 1, #loosies)

			local curLoose = loosies[current]

			local added
			-- first check for easy 2 piece outlines
			for l = #loosies, 1, -1 do
				local loose = loosies[l]
				if l ~= current then
					local side = (curLoose[#curLoose] == loose[1] and curLoose[1] == loose[#loose]) and 1
						or ((curLoose[#curLoose] == loose[#loose] and curLoose[1] == loose[1]) and -1 or 0)

					if side ~= 0 then
						if side == 1 then
							for i = 1, #loose, 1 do
								table.insert(curLoose, loose[i])
							end
						else
							for i = #loose, 1, -1 do
								table.insert(curLoose, loose[i])
							end
						end
						table.remove(loosies, l)
						table.remove(loosies, current)

						table.insert(outlines, curLoose)

						-- DEBUG
						-- offset=offset+1
						-- local test = {color={r=0,g=0,b=1}}
						-- for p,pt in ipairs(curLoose) do
						--   table.insert(test,pt.x)
						--   table.insert(test,pt.y+offset*40)
						-- end
						-- table.insert(testpolys,test)

						nada = false
						goto skip
					end
				end
			end

			-- join unjoined loose lines to current
			for l = #loosies, 1, -1 do
				local loose = loosies[l]
				if l ~= current then
					if curLoose[#curLoose] == loose[1] then
						added = added and 0 or l
						nada = false
					elseif curLoose[#curLoose] == loose[#loose] then
						added = added and 0 or -l
						nada = false
					end
				end
			end

			added = added or 0

			if added ~= 0 then
				-- DEBUG
				-- offset=offset+1
				-- local test = {color={r=0,g=0,b=1}}
				-- for p,pt in ipairs(curLoose) do
				--   table.insert(test,pt.x)
				--   table.insert(test,pt.y+offset*40)
				-- end
				-- table.insert(testpolys,test)

				-- local test = {color={r=1,g=0,b=0}}
				-- for p,pt in ipairs(loosies[added]) do
				--   table.insert(test,pt.x)
				--   table.insert(test,pt.y+offset*40)
				-- end
				-- table.insert(testpolys,test)

				-- for l,testshape in ipairs(loosies) do
				--   local test = {color={r=0,g=1,b=0}}
				--   for p,pt in ipairs(testshape) do
				--     table.insert(test,pt.x+3)
				--     table.insert(test,pt.y+offset*40+3)
				--   end
				--   table.insert(testpolys,test)
				-- end

				if added > 0 then
					for i = 1, #loosies[added], 1 do
						table.insert(curLoose, loosies[added][i])
					end
					table.remove(loosies, added)
				else
					for i = #loosies[-added], 1, -1 do
						table.insert(curLoose, loosies[-added][i])
					end
					table.remove(loosies, -added)
				end
			end

			if curLoose[#curLoose] == curLoose[1] then
				table.insert(outlines, curLoose)
				table.remove(loosies, 1)
			else
				if added == 0 then
					current = current + 1
				end
			end

			if current == 1 and nada then
				print("FUCK, loosies ", #loosies)
				break
			end

			if current == 1 then
				nada = true
			end
			::skip::
		end
		_tock("stitch")

		-- DEBUG
		-- if #loosies>0 then
		--   for l=#loosies,1,-1 do
		--       local testshape = {}
		--       local loosie = loosies[l]
		--       for p,pt in ipairs(loosie) do
		--         table.insert(testshape,pt.x)
		--         table.insert(testshape,pt.y)
		--       end
		--       table.insert(testpolys,testshape)
		--   end
		--   print(" loosies2",#loosies)
		-- end

		if #outlines > 0 then
			-- calc bounding box of outlines, set largest first
			local largest = 0
			local outer = 1
			local BBOX
			for o = #outlines, 1, -1 do
				local outline = outlines[o]
				local bbox = { outline[1].x, outline[1].y, outline[1].x, outline[1].y }
				for l = 1, #outline, 1 do
					bbox[1] = min(bbox[1], outline[l].x)
					bbox[2] = min(bbox[2], outline[l].y)
					bbox[3] = max(bbox[3], outline[l].x)
					bbox[4] = max(bbox[4], outline[l].y)
				end
				local size = abs(bbox[4] - bbox[2]) * abs(bbox[3] - bbox[1])
				outlines[o].size = size
				if size > largest then
					BBOX = bbox
					largest = size
				end
			end
			table.sort(outlines, function(a, b)
				return a.size > b.size
			end)

			-- preflip all ccw outlines to clockwise and remove smol ones
			for o = #outlines, 1, -1 do
				local outer = outlines[o]
				if outer.size < (panel.minHoleSize or 0.01) then
					print("removed small fill shape of size", outer.size)
					if o == 1 then
						goto continue
					end
					table.remove(outlines, o)
				end
			end

			-- DEBUG
			-- if #outlines>1  then
			--   for o,outline in ipairs(outlines) do
			--     local testshape = {}
			--     for p,pt in ipairs(outline) do
			--       table.insert(testshape,pt.x)
			--       table.insert(testshape,pt.y)
			--     end

			--     testshape.color= outline.cw and {r=1,g=0,b=0} or {r=0,g=0,b=1}
			--     table.insert(testpolys,testshape)
			--   end
			-- end

			_tick("finalize")
			-- match curves
			for o = #outlines, 1, -1 do
				local outline = outlines[o]
				local cx, cy = 0, 0
				for i = 1, #outline - 1, 1 do
					local a, b = outline[i], outline[i + 1]
					local matched = false
					-- TODO make matching work properly
					-- for s=1,segments[0][0],1 do
					--   local seg = segments[s]
					--   if a.x==seg[1] and a.y==seg[2] and b.x==seg[3] and b.y==seg[4] then
					--     outline[i]  = {seg[1],seg[2],cx,cy,seg[5],seg[6]}
					--     cx,cy = seg[7],seg[8]
					--     if i==#outline-1 then
					--       outline[i+1]  = {seg[3],seg[4],cx,cy,0,0}
					--     end
					--     matched=true
					--     break
					--   elseif a.x==seg[3] and a.y==seg[4] and b.x==seg[1] and b.y==seg[2] then
					--     outline[i]  = {seg[3],seg[4],cx,cy,seg[7],seg[8]}
					--     cx,cy = seg[5],seg[6]
					--     if i==#outline-1 then
					--       outline[i+1]  = {seg[1],seg[2],cx,cy,0,0}
					--     end
					--     matched=true
					--     break
					--   end
					-- end
					-- if (not matched) then
					outline[i] = { outline[i].x, outline[i].y, 0, 0, 0, 0 }
					cx, cy = 0, 0
					-- end
				end
				if type(outline[#outline]) == "cdata" then
					outline[#outline] = { outline[#outline].x, outline[#outline].y, 0, 0, 0, 0 }
				end
				-- outlines[o] = out
				-- outline[1][3],outline[1][4] = cx,cy
				outline.ox = debugw * 2
				outline.oy = debugh
				local size = outline.size
				local out = newStrokeLineFromTable(outline)
				if out then
					outlines[o] = out
					outlines[o][0][3] = size
					table.insert(testpolys, outline)
				else
					print("OUTLINE FAILED")
					table.remove(outlines, o)
				end

				-- _time("grouped region "..tostring(math.floor(size)))
			end
			table.insert(shapes, { bbox = BBOX, line = outlines, point = somePointInShape })
			_tock("finalize")
		end

		-- sort by size

		-- error("ran fine")

		if startPoint then
			break
		end
	end
	_tell_time("cluster")
	_tell_time("dupes")
	_tell_time("outers")
	_tell_time("trails")
	_tell_time("grid")
	_tell_time("join")
	_tell_time("stitch")
	_tell_time("finalize")
	table.sort(shapes, function(a, b)
		return a.line[1][0][3] < b.line[1][0][3]
	end)
	_enable_timers()
	_end_timer("group triangles")
	return shapes
end
