local red, green, blue, purple, orange, lightblue =
	{ r = 1, g = 0, b = 0 },
	{ r = 0, g = 1, b = 0 },
	{ r = 0, g = 0, b = 1, a = 0.5 },
	{ r = 0.95, g = 0.6, b = 1 },
	{ r = 1, g = 0.6, b = 0.2 },
	{ r = 0.7, g = 0.7, b = 1 }
local gridw, gridh = 8, 8

function sameColor(a, b)
	return math.abs(a.r - b.r) < 0.001
		and math.abs(a.g - b.g) < 0.001
		and math.abs(a.b - b.b) < 0.001
		and math.abs(a.a - b.a) < 0.001
end

local function cross(pa, pb, pc, b, c, normal)
	local bx = b.x
	local by = b.y
	local cyby = c.y - by
	local cxbx = c.x - bx
	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(t0a, t0b, t0c, t1a, t1b, t1c)
	local normal0 = (t0b.x - t0a.x) * (t0c.y - t0a.y) - (t0b.y - t0a.y) * (t0c.x - t0a.x)
	local normal1 = (t1b.x - t1a.x) * (t1c.y - t1a.y) - (t1b.y - t1a.y) * (t1c.x - t1a.x)

	return not (
			cross(t1a, t1b, t1c, t0a, t0b, normal0)
			or cross(t1a, t1b, t1c, t0b, t0c, normal0)
			or cross(t1a, t1b, t1c, t0c, t0a, normal0)
			or cross(t0a, t0b, t0c, t1a, t1b, normal1)
			or cross(t0a, t0b, t0c, t1b, t1c, normal1)
			or cross(t0a, t0b, t0c, t1c, t1a, normal1)
		)
end

local function ptTri(px, py, x1, y1, x2, y2, x3, y3)
	return onSameSide2(px, py, x1, y1, x2, y2, x3, y3)
		and onSameSide2(px, py, x2, y2, x1, y1, x3, y3)
		and onSameSide2(px, py, x3, y3, x1, y1, x2, y2)
end

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

local function mdist(x1, y1, x2, y2)
	return math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1))
end
local function pointOnTri(tri, px, py)
	return
		math.abs(
		mdist(tri[1], tri[2], tri[3], tri[4]) - (mdist(tri[1], tri[2], px, py) + mdist(px, py, tri[3], tri[4]))
	) < 0.0000001 or math.abs(
		mdist(tri[5], tri[6], tri[3], tri[4]) - (mdist(tri[5], tri[6], px, py) + mdist(px, py, tri[3], tri[4]))
	) < 0.0000001 or math.abs(
		mdist(tri[1], tri[2], tri[5], tri[6]) - (mdist(tri[1], tri[2], px, py) + mdist(px, py, tri[5], tri[6]))
	) < 0.0000001
end
local function flip(t)
	local n = t[0][0]
	local i = 1
	while i < n do
		t[i][1], t[n][1] = t[n][1], t[i][1]
		t[i][2], t[n][2] = t[n][2], t[i][2]
		i = i + 1
		n = n - 1
	end
	return t
end
local function dotprod(a1x, a1y, a2x, a2y, b1x, b1y, b2x, b2y)
	local ax, ay, bx, by = a2x - a1x, a2y - a1y, b2x - b1x, b2y - b1y
	local alen, blen = math.sqrt(ax * ax + ay * ay), math.sqrt(bx * bx + by * by)
	ax, ay, bx, by = ax / alen, ay / alen, bx / blen, by / blen
	return ax * bx + ay * by
end

local function joinSegs(segs, bbox, epsilon, brushDebug)
	-- local epsilon = 0.01
	local doffx = (bbox[3] - bbox[1]) + 10
	local doffx4 = doffx * 1
	local tw, th = (bbox[3] - bbox[1]) / gridw, (bbox[4] - bbox[2]) / gridh
	-- seg grid to speed up joining
	_time()
	-- print("sgrid",gridw,gridh,segs[0][0])
	local sgrid = ffi.new(string.format("u16[%i][%i][%i]", gridw, gridh, segs[0][0]))

	for s = 1, segs[0][0], 1 do
		local seg = segs[s]
		local x1, y1 = math.floor((seg[1] - bbox[1]) / tw), math.floor((seg[2] - 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[3] - bbox[1]) / tw), math.floor((seg[4] - 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

	-- join segs into line
	local loosies = {}
	local used = ffi.new("bool[?]", segs[0][0] + 1)
	local left, right = ffi.new(STROKE_TYPE, segs[0][0] * 2), ffi.new(STROKE_TYPE, segs[0][0] * 2)
	-- print("left right",segs[0][0])
	for s1 = 1, segs[0][0], 1 do
		if not used[s1] then
			local seg1 = segs[s1]
			used[s1] = true
			left[0][0], right[0][0] = 1, 1
			left[0][1], right[0][1] = seg1[18] / 2, seg1[18] / 2
			left[1][1], left[1][2] = seg1[1], seg1[2]
			right[1][1], right[1][2] = seg1[3], seg1[4]

			local canAddLeft, canAddRight = 0, 0
			for _ = 1, segs[0][0], 1 do
				local l, r = left[0][0], right[0][0]
				-- print(l,r,segs[0][0])
				local join = false
				local addLeft, addRight = canAddLeft, canAddRight
				local lx, ly, rx, ry
				-- only add if 1 found

				--check left
				local gx, gy = mf((left[l][1] - bbox[1]) / tw), mf((left[l][2] - bbox[2]) / th)
				local tile = sgrid[gy][gx]
				for i = 1, tile[0], 1 do
					local s2 = tile[i]
					if s1 ~= s2 and not used[s2] then
						local seg2 = segs[s2]
						if addLeft > -1 then
							local a, b
							if epsilon > 0 then
								a, b =
									math.dist(left[l][1], left[l][2], seg2[1], seg2[2]) < epsilon,
									math.dist(left[l][1], left[l][2], seg2[3], seg2[4]) < epsilon
							else
								a, b =
									left[l][1] == seg2[1] and left[l][2] == seg2[2],
									left[l][1] == seg2[3] and left[l][2] == seg2[4]
							end
							if a or b then
								if addLeft > 0 then
									addLeft = -1
								-- print("branch")
								else
									addLeft = s2
									lx = a and seg2[3] or seg2[1]
									ly = a and seg2[4] or seg2[2]
								end
							end
						end
					end
				end

				--check right
				gx, gy = mf((right[r][1] - bbox[1]) / tw), mf((right[r][2] - bbox[2]) / th)
				tile = sgrid[gy][gx]
				for i = 1, tile[0], 1 do
					local s2 = tile[i]
					if s1 ~= s2 and not used[s2] then
						local seg2 = segs[s2]
						if addRight > -1 then
							local c, d
							if epsilon > 0 then
								c, d =
									math.dist(right[r][1], right[r][2], seg2[1], seg2[2]) < epsilon,
									math.dist(right[r][1], right[r][2], seg2[3], seg2[4]) < epsilon
							else
								c, d =
									right[r][1] == seg2[1] and right[r][2] == seg2[2],
									right[r][1] == seg2[3] and right[r][2] == seg2[4]
							end
							if c or d then
								if addRight > 0 then
									addRight = -1
								-- print("branch")
								else
									addRight = s2
									rx = c and seg2[3] or seg2[1]
									ry = c and seg2[4] or seg2[2]
								end
							end
						end
					end
				end

				--add if okay
				local s = 0.2
				if addLeft > 0 and not used[addLeft] then
					used[addLeft] = true

					if not (left[left[0][0]][1] == lx and left[left[0][0]][2] == ly) then
						left[0][1] = left[0][1] + math.dist(left[left[0][0]][1], left[left[0][0]][2], lx, ly)
						left[0][0] = left[0][0] + 1
						left[left[0][0]][1], left[left[0][0]][2] = lx, ly
						if math.dist(lx, ly, left[1][1], left[1][2]) < 0.000001 then
							print("DEAD THAT////////////////////////////")
							canAddLeft = -1
						end
					end
				end
				if addRight > 0 and not used[addRight] then
					used[addRight] = true

					if not (right[right[0][0]][1] == rx and right[right[0][0]][2] == ry) then
						right[0][1] = right[0][1] + math.dist(right[right[0][0]][1], right[right[0][0]][2], rx, ry)
						right[0][0] = right[0][0] + 1
						right[right[0][0]][1], right[right[0][0]][2] = rx, ry
						if math.dist(rx, ry, right[1][1], right[1][2]) < 0.000001 then
							print("DEAD THAT////////////////////////////")
							canAddRight = -1
						end
					end
				end
				if not (addLeft > 0 or addRight > 0) then
					break
				end
			end

			flip(left)
			concat2(left, right)
			local length = left[0][1] + right[0][1]
			local len = left[0][0] + right[0][0] + 1
			local new = ffi.new(STROKE_TYPE, len + 1)
			ffi_copy(new, left, len * STROKEPT_SIZE)
			new[0][1] = length
			if length > 0 then
				table.insert(loosies, new)
			end
		end
	end
	_time("convert to line")

	if panel.debugView and epsilon == 0 then
		-- print("loosies",#loosies)
		for l, loose in ipairs(loosies) do
			local ox, oy = 0, 0
			local r, g, b = hsvToRgb(math.random(), 0.8, 0.7)
			local randomColor = { r = r, g = g, b = b, a = 1 }
			local test = { color = randomColor }
			for i = 1, loose[0][0], 1 do
				-- local ox,oy = math.random()*1,math.random()*1
				table.insert(test, loose[i][1] - doffx * 3 + ox)
				table.insert(test, loose[i][2] + oy)
			end
			fill_addTestLines({ test })
		end
	end

	-- trim flaps
	for l, loose in ipairs(loosies) do
		-- if loose[0][0]>2 then
		--  if (loose[1][1] == loose[3][1] and loose[1][1] == loose[3][1]) then
		--    print("DELETE FLAP")
		--    copyFillSeg(loose[loose[0][0]],loose)
		--    table.remove(loose,1)
		--  end
		--  if loose[loose[0][0]-2] and (loose[loose[0][0]][1]==loose[loose[0][0]-2][1] and loose[loose[0][0]][2]==loose[loose[0][0]-2][2]) then
		--    print("DELETE FLAP")
		--    table.remove(loose,#loose)
		--  end
		-- end

		if loose[0][0] > 2 then
			local a, b, c = loose[1], loose[2], loose[3]
			local adot = dotprod(a[1], a[2], b[1], b[2], b[1], b[2], c[1], c[2])
			if adot < -0.99 then
				-- print("adot",adot)
				-- print(a[1],a[2],b[1],b[2])
				-- print(b[1],b[2],c[1],c[2])
				if brushDebug then
					fill_addTestLines({
						{ color = orange, a[1] - doffx4, a[2] - 1, b[1] - doffx4, b[2] },
					})
					fill_addTestLines({
						{ color = orange, b[1] - doffx4, b[2] - 1, c[1] - doffx4, c[2] },
					})
				end
				loose[2], loose[3] = loose[3], loose[2]
				-- for i=2,loose[0][0]-1,1 do
				--   loose[i]=loose[i+1]
				-- end
				-- loose[0][0]=loose[0][0]-1
			end
			local a, b, c = loose[loose[0][0]], loose[loose[0][0] - 1], loose[loose[0][0] - 2]
			local adot = dotprod(a[1], a[2], b[1], b[2], b[1], b[2], c[1], c[2])
			if adot < -0.99 then
				if brushDebug then
					fill_addTestLines({
						{ color = orange, a[1] - doffx4 + 2, a[2], b[1] - doffx4 + 2, b[2] },
					})
				end
				loose[0][0] = loose[0][0] - 1
			end
		end
	end
	_time("trim flaps")

	-- join loose lines
	local out = {}
	local joined = true
	local time1, time2, time3 = 0, 0, 0
	local n = #loosies > 512 and 32 or (#loosies > 256 and 16 or (#loosies > 128 and 8 or 2))
	local lgridw, lgridh = n, n
	local gridLimit = 256 + #loosies * 4
	local grid = ffi.new(string.format("int[%i][%i][%i]", lgridw, lgridh, gridLimit))

	local function dist(x1, y1, x2, y2)
		return math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1))
	end
	local function clamp(n, low, high)
		return math.min(math.max(n, low), high)
	end
	while joined do
		local tw, th = (bbox[3] - bbox[1]) / lgridw, (bbox[4] - bbox[2]) / lgridh
		local tn = math.max(1, math.floor(8 / tw))
		joined = false
		-- print("tw,th",tw,th)
		-- mark lines as loop if dist between ends is <5% of lines length and <10 in absolute dist

		for l, loose in ipairs(loosies) do
			local a1, a2 = loose[1], loose[loose[0][0]]
			local d = math.dist(a1[1], a1[2], a2[1], a2[2])
			if d < 3 and d < loose[0][1] * 0.05 then
				-- print("loop",d)
				loose[0][2] = 2
				if d > 0 then
					local a, b, c, d = loose[1], loose[2], loose[loose[0][0]], loose[loose[0][0] - 1]
					local dot = dotprod(a[1], a[2], b[1], b[2], c[1], c[2], d[1], d[2])
					if dot < 0.9 then
						local mx, my = (a[1] + c[1]) / 2, (a[2] + c[2]) / 2
						loose[1][1], loose[1][2] = mx, my
						loose[loose[0][0]][1], loose[loose[0][0]][2] = mx, my
					else
						segs[0][0] = segs[0][0] + 1
						local seg = segs[segs[0][0]]
						seg[1], seg[2], seg[3], seg[4] = a1[1], a1[2], a2[1], a2[2]
						loose[0][0] = loose[0][0] + 1
						loose[loose[0][0]][1], loose[loose[0][0]][2] = a1[1], a1[2]
						if brushDebug then
							local o = 3
							fill_addTestLines({
								{
									color = green,
									seg[1] - doffx4 + o,
									seg[2] + o,
									seg[3] - doffx4 + o,
									seg[4] + o,
									seg[3] - doffx4 + o,
									seg[4] + o,
								},
							})
						end
					end
				end
			end
		end

		-- look for closest gap between any two line ends
		local closest = 20
		local joins1, joins2, joins3, joins4 = false

		for x = 0, lgridw - 1, 1 do
			for y = 0, lgridh - 1, 1 do
				grid[y][x][0] = 0
			end
		end

		-- local gridstr = ""
		-- for x=0,lgridw-1,1 do for y=0,lgridh-1,1 do gridstr=gridstr..tostring(grid[y][x][0]).."," end gridstr=gridstr.."\n" end
		-- print("lgrid1")
		-- print(gridstr)

		for l, loose in ipairs(loosies) do
			local a, b = loose[1], loose[loose[0][0]]
			local ax, ay = math.floor((a[1] - bbox[1]) / tw), math.floor((a[2] - bbox[2]) / th)
			local x1, x2 = math.max(ax - tn, 0), math.min(ax + tn, lgridw - 1)
			local y1, y2 = math.max(ay - tn, 0), math.min(ay + tn, lgridh - 1)
			for x = x1, x2, 1 do
				for y = y1, y2, 1 do
					grid[y][x][0] = grid[y][x][0] + 1
					if grid[y][x][0] > gridLimit then
						error(string.format("grid too small %i / %i", grid[y][x][0], gridLimit))
					end
					grid[y][x][grid[y][x][0]] = l
				end
			end

			ax, ay = math.floor((b[1] - bbox[1]) / tw), math.floor((b[2] - bbox[2]) / th)
			x1, x2 = math.max(ax - tn, 0), math.min(ax + tn, lgridw - 1)
			y1, y2 = math.max(ay - tn, 0), math.min(ay + tn, lgridh - 1)
			for x = x1, x2, 1 do
				for y = y1, y2, 1 do
					grid[y][x][0] = grid[y][x][0] + 1
					if grid[y][x][0] > gridLimit then
						error(string.format("grid too small %i / %i", grid[y][x][0], gridLimit))
					end
					grid[y][x][grid[y][x][0]] = -l
				end
			end
		end
		-- local gridstr = ""
		-- for x=0,lgridw-1,1 do for y=0,lgridh-1,1 do gridstr=gridstr..tostring(grid[y][x][0]).."," end gridstr=gridstr.."\n" end
		-- print("lgrid2")
		-- print(gridstr)

		for l1 = 1, #loosies, 1 do
			local loose1 = loosies[l1]
			if loose1[0][2] < 1 then
				local a, b = loose1[1], loose1[loose1[0][0]]
				for e = 1, 2, 1 do
					local e1 = e == 1 and a or b
					local x, y = math.floor((e1[1] - bbox[1]) / tw), math.floor((e1[2] - bbox[2]) / th)
					local tile = grid[y][x]
					for i = 1, tile[0], 1 do
						local head = tile[i] > 0
						local l2 = head and tile[i] or -tile[i]
						if l1 ~= l2 then
							local loose2 = loosies[l2]
							if loose2 and loose2[0][2] < 1 then
								local e2 = loose2[head and 1 or loose2[0][0]]
								local d = dist(e1[1], e1[2], e2[1], e2[2])
								if d < closest then
									closest = d
									if e == 1 and head then --d1
										joins1, joins2, joins3, joins4 = l1, l2, true, false
									elseif e == 1 and not head then --d2
										joins1, joins2, joins3, joins4 = l2, l1, false, false
									elseif e == 2 and head then --d3
										joins1, joins2, joins3, joins4 = l1, l2, false, false
									elseif e == 2 and not head then --d3
										joins1, joins2, joins3, joins4 = l1, l2, false, true
									end
								end
							end
						end
					end
				end
			end
		end

		-- join em
		if joins1 then
			-- print("join",joins1,joins2,joins3,joins4,closest)
			local a, b = loosies[joins1], loosies[joins2]
			-- print("ab testing",inspect(a[a[0][0]]),inspect(b[b[0][0]]))
			if joins3 then
				flip(a)
			end
			if joins4 then
				flip(b)
			end
			segs[0][0] = segs[0][0] + 1
			local seg = segs[segs[0][0]]
			seg[1], seg[2], seg[3], seg[4] = a[a[0][0]][1], a[a[0][0]][2], b[1][1], b[1][2]
			-- print(seg[1],seg[2],seg[3],seg[4])
			-- if brushDebug then
			--   local o =1
			--   editor_addTestLines({{color=red,
			--    seg[1]-doffx4+o,seg[2]+o,
			--    seg[3]-doffx4+o,seg[4]+o,
			--    seg[3]-doffx4+o,seg[4]+o
			--   }})
			-- end
			local length = a[0][1] + b[0][1]
			local len = a[0][0] + b[0][0] + 1
			local new = ffi.new(STROKE_TYPE, len + 1)
			concat2(new, a)
			concat2(new, b)
			new[0][1] = length
			loosies[joins1] = new
			table.remove(loosies, joins2)
			joined = true
		end
	end
	_time("join")

	-- remove overlapping segments
	if epsilon > 0 then
		for l, loose in lume.ripairs(loosies) do
			if loose[0][2] < 1 then
				local pt1, pt2 = loose[1], loose[loose[0][0]]
				if math.max(math.abs(pt1[1] - pt2[1]), math.abs(pt1[2] - pt2[2])) < 0.1 then
					loose[0][2] = 1
				else
					-- print("unlooped, last try")
					local pt1, pt2, pt3, pt4 = loose[1], loose[2], loose[loose[0][0] - 1], loose[loose[0][0]]
					local d1 = distPointToLineSegment2(pt1[1], pt1[2], pt3[1], pt3[2], pt4[1], pt4[2])
					local d2 = distPointToLineSegment2(pt4[1], pt4[2], pt1[1], pt1[2], pt2[1], pt2[2])
					-- print("d1,d2",d1,d2)
					if d1 < 0.25 and d2 < 0.25 then
						local mx, my = (pt1[1] + pt4[1]) / 2, (pt1[2] + pt4[2]) / 2
						pt1[1], pt1[2] = mx, my
						pt4[1], pt4[2] = mx, my
						loose[0][2] = 1
					else
						-- print("cant loop, yeet into trash")
						-- table.remove(loosies,l)
					end
				end
			end
		end
	end

	-- for l,loose in ipairs(loosies) do
	--   for i=2,loose[0][0]-1,1 do
	--     local pt1,pt2 = loose[1],loose[i]
	--     if math.max(math.abs(pt1[1]-pt2[1]),math.abs(pt1[2]-pt2[2]))<0.00001 then
	--       -- editor_addTestLines({{color=red,
	--       --  pt1[1]-2,pt1[2]-2,
	--       --  pt1[1]+2,pt1[2]+2,
	--       --  pt1[1]-2,pt1[2]+2,
	--       --  pt1[1]+2,pt1[2]-2
	--       -- }})

	--       -- local new = ffi.new(STROKE_TYPE,i+1)
	--       -- ffi_copy(new1,loose,STROKEPT_SIZE*(i+1))
	--       -- new[0][0]=i
	--       -- loosies[l]=new
	--       break
	--     end
	--   end
	-- end

	if brushDebug then
		-- print("loosies",#loosies)
		for l, loose in ipairs(loosies) do
			local ox, oy = 0, 0
			local r, g, b = hsvToRgb(math.random(), 0.8, 0.7)
			local randomColor = { r = r, g = g, b = b, a = 1 }
			local test = { color = randomColor }
			for i = 1, loose[0][0], 1 do
				-- local ox,oy = math.random()*1,math.random()*1
				table.insert(test, loose[i][1] - doffx4 + ox)
				table.insert(test, loose[i][2] + oy)
			end
			fill_addTestLines({ test })
		end
	end

	for l, loose in ipairs(loosies) do
		local a1, a2 = loose[1], loose[loose[0][0]]
		if not (a1[1] == a2[1] and a1[2] == a2[2]) then
			local a, b, c, d = loose[1], loose[2], loose[loose[0][0]], loose[loose[0][0] - 1]
			local dot = dotprod(a[1], a[2], b[1], b[2], c[1], c[2], d[1], d[2])
			if dot < 0.9 then
				local mx, my = (a[1] + c[1]) / 2, (a[2] + c[2]) / 2
				loose[1][1], loose[1][2] = mx, my
				loose[loose[0][0]][1], loose[loose[0][0]][2] = mx, my
			else
				-- segs[0][0]=segs[0][0]+1
				-- local seg = segs[segs[0][0]]
				-- seg[1],seg[2],seg[3],seg[4] = a1[1],a1[2],a2[1],a2[2]
				loose[0][0] = loose[0][0] + 1
				loose[loose[0][0]][1], loose[loose[0][0]][2] = a1[1], a1[2]
				if brushDebug then
					local o = 1
					-- editor_addTestLines({{color=red,
					--  seg[1]-doffx4+o,seg[2]+o,
					--  seg[3]-doffx4+o,seg[4]+o,
					--  seg[3]-doffx4+o,seg[4]+o
					-- }})
				end
			end
			-- print("linkup")
		end
	end

	-- THIS IS THE SOURCE OF THE DISRESPECT
	-- AND THE DEGRADATION
	if epsilon > 0 then
		for l, loose in ipairs(loosies) do
			local fixed = false
			if loose[0][0] > 3 then
				-- print("chehk folds")
				local lim = 0
				local A, B = 1, loose[0][0]
				for i = A, B, 1 do
					local ai, bi, ci, di = wrap2(i - 1, A, B), wrap2(i, A, B), wrap2(i + 1, A, B), wrap2(i + 2, A, B)
					local a, b, c, d = loose[ai], loose[bi], loose[ci], loose[di]
					local adot = dotprod(a[1], a[2], b[1], b[2], b[1], b[2], c[1], c[2])
					local bdot = dotprod(b[1], b[2], c[1], c[2], c[1], c[2], d[1], d[2])
					-- print("f",adot,bdot,math.acos(adot),math.acos(bdot))
					-- print(adot,bdot)
					if adot < -0.5 and bdot < -0.5 then
						-- print("FOOLDDDD////////////////////////////////")
						loose[bi], loose[ci] = loose[ci], loose[di]
						fixed = true
						-- editor_addTestLines({{color=orange,
						--  loose[ai][1]-2,loose[ai][2]-2,
						--  loose[ai][1]+2,loose[ai][2]+2
						-- }})
						print("FIXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX1")
					elseif adot < -0.999 then
						-- print("clip this",a[1],a[2],b[1],b[2],c[1],c[2],d[1],d[2])
						-- print(math.dist(a[1],a[2],b[1],b[2]),math.dist(b[1],b[2],c[1],c[2]),math.dist(c[1],c[2],d[1],d[2]))
						b[1], b[2] = a[1], a[2]
						c[1], c[2] = a[1], a[2]
						fixed = true
					-- editor_addTestLines({{color=red,
					--  loose[ai][1]-2,loose[ai][2]-2,
					--  loose[ai][1]+2,loose[ai][2]+2
					-- }})
					-- print("FIXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX2")
					elseif bdot < -0.999 then
						a[1], a[2] = b[1], b[2]
						fixed = true
						-- d[1],d[2] = b[1],b[2]
						-- editor_addTestLines({{color=blue,
						--  loose[ai][1]+2,loose[ai][2]-2,
						--  loose[ai][1]-2,loose[ai][2]+2
						-- }})
						-- print("FIXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX3")
					end
				end
			end
		end

		for l, loose in ipairs(loosies) do
			if loose[0][0] > 2 then
				local shift = 0
				for i = 1, loose[0][0] - 1, 1 do
					if i < loose[0][0] then
						local mx = math.dist(loose[i][1], loose[i][2], loose[i + 1][1], loose[i + 1][2])
						if mx < 0.5 then
							shift = shift + 1
							loose[0][0] = loose[0][0] - 1
						end
						if shift > 0 then
							-- print(mx,shift)
							fill_copyFillSeg(loose[i + shift], loose[i])
						end
					end
				end
			end
		end
	end

	for l, loose in lume.ripairs(loosies) do
		local clip = true
		for _ = 1, 2, 1 do
			clip = false
			if loose[0][0] > 3 and loose[loose[0][0]][1] == loose[1][1] and loose[loose[0][0]][2] == loose[1][2] then
				if loose[loose[0][0] - 1][1] == loose[2][1] and loose[loose[0][0] - 1][2] == loose[2][2] then
					local pt1 = loose[2]
					if brushDebug then
						fill_addTestLines({
							{
								color = red,
								pt1[1] - 2,
								pt1[2] - 2,
								pt1[1] + 2,
								pt1[2] + 2,
								pt1[1] - 2,
								pt1[2] + 2,
								pt1[1] + 2,
								pt1[2] - 2,
							},
						})
					end
					clip = true
					local new = ffi.new(STROKE_TYPE, loose[0][0] + 1)
					new[0][0] = loose[0][0] - 2
					for i = 1, new[0][0], 1 do
						new[i] = loose[i + 1]
					end
					loosies[l] = new
				else
					loose[0][0] = loose[0][0] - 1
				end
			else
				-- table.remove(loosies,l)
			end
		end
	end
	_time("joined")

	return loosies
end

local function calcBoxesAndLargest(loosies)
	local bboxes = {}
	for l, loose in lume.ripairs(loosies) do
		local bbox = ffi.new("float[5]")
		bbox[1], bbox[2], bbox[3], bbox[4] = loose[1][1], loose[1][2], loose[1][1], loose[1][2]
		for i = 1, loose[0][0], 1 do
			bbox[1] = math.min(bbox[1], loose[i][1])
			bbox[2] = math.min(bbox[2], loose[i][2])
			bbox[3] = math.max(bbox[3], loose[i][1])
			bbox[4] = math.max(bbox[4], loose[i][2])
		end
		loose[0][2] = (bbox[3] - bbox[1]) * (bbox[4] - bbox[2])
		if loose[0][2] < 0.01 then
			table.remove(loosies, l)
		else
			loose[0][3], loose[0][4], loose[0][5], loose[0][6] = bbox[1], bbox[2], bbox[3], bbox[4]
		end
	end

	local size = 0
	local largest
	for l, loose in ipairs(loosies) do
		-- print("loose[0][2]",l,loose[0][2])
		if loose[0][2] > size then
			size = loose[0][2]
			largest = l
		end
	end
	-- print("loosies[1],loosies[largest]",loosies[1],loosies[largest])
	-- if not loosies[1] and not loosies[largest] then
	--   return false
	-- end
	if loosies[1] and loosies[largest] then
		loosies[1], loosies[largest] = loosies[largest], loosies[1]
	else
		-- error("cant set largest first")
		return false
	end
	-- print("largest",largest,loosies[largest][0][2])
	return true
end

local function removeOperatedStrokes(panel, shape)
	for s = #panel.f.strokes, 1, -1 do
		if panel.f.strokes[s].remove then
			if panel.f.strokes[s] ~= shape then
				table.insert(panel.strokeDiffs.removed, { frame = panel.f, stroke = panel.f.strokes[s] })
			end
			table.remove(panel.f.strokes, s)
		end
	end
end
local function debugLoosies(loosies, debug, ox, oy)
	if debug then
		local ox, oy = ox or 0, oy or 0
		-- print("loosies",#loosies)
		for l, loose in ipairs(loosies) do
			local r, g, b = hsvToRgb(math.random(), 0.8, 0.7)
			local randomColor = { r = r, g = g, b = b, a = 1 }
			local test = { color = randomColor }
			for i = 1, loose[0][0], 1 do
				-- local ox,oy = math.random()*1,math.random()*1
				table.insert(test, loose[i][1] + ox)
				table.insert(test, loose[i][2] + oy)
			end
			table.insert(test, loose[1][1] + ox)
			table.insert(test, loose[1][2] + oy)
			fill_addTestLines({ test })
		end
	end
end

local function loosiesToShapes(loosies, newShapes)
	local newShapes = newShapes or {}
	table.sort(loosies, function(a, b)
		return a[0][2] > b[0][2]
	end)
	local outerHash = {}
	local innerHash = {}
	-- group outlines with their holes
	for l1 = #loosies, 1, -1 do
		local loose1 = loosies[l1]
		for l2 = 1, #loosies, 1 do
			local loose2 = loosies[l2]
			if
				l1 ~= l2
				and not outerHash[loose1]
				and not innerHash[loose2]
				and loose1[0][3] < loose2[0][5]
				and loose2[0][3] < loose1[0][5]
				and loose1[0][4] < loose2[0][6]
				and loose2[0][4] < loose1[0][6]
			then
				if ffi.C.pnpoly2(loose2[0][0], loose2, loose1[1][1], loose1[1][2]) % 2 ~= 0 then
					if outerHash[loose2] then
						table.insert(outerHash[loose2], newStrokeLineFromCdata(loose1))
						innerHash[loose1] = true
					else
						table.insert(newShapes, {
							newStrokeLineFromCdata(loose2),
							newStrokeLineFromCdata(loose1),
						})
						outerHash[loose2] = newShapes[#newShapes]
						innerHash[loose1] = true
					end
					break
				end
			end
		end
	end
	-- add remaining ones without holes
	for l, loose in ipairs(loosies) do
		-- print(l,(outerHash[loose] or innerHash[loose]))
		if not (outerHash[loose] or innerHash[loose]) then
			table.insert(newShapes, { newStrokeLineFromCdata(loose) })
		end
	end
	return newShapes
end

local function splitFillWithInts(fill, ints)
	local R = 0
	for f = 1, #fill.line, 1 do
		local line = fill.line[f]
		local n = 1
		local new = newStrokeLine(line[0][0] + #ints)
		-- new[1][1],new[1][2] = line[1][1],line[1][2]
		for p = 1, line[0][0], 1 do
			local l1, l2 = line[p], line[p + 1]
			for i = #ints, 1, -1 do
				local int = ints[i]
				local da, db, len =
					math.dist(int[1], int[2], l1[1], l1[2]),
					math.dist(int[1], int[2], l2[1], l2[2]),
					math.dist(l2[1], l2[2], l1[1], l1[2])
				if (da < len and db < len) and da + db <= len + 0.0000001 then
					print(da, db, len)
					-- print("insert point ------------------====================================================")
					-- if animatePanel.debugView then
					-- print(int[1],int[2])
					-- R=R+1
					-- local x,y = int[1],int[2]
					-- editor_addTestLines({{color = {r=R/4,g=0,b=0}, x-2,y-2, x+2,y+2 }})
					-- editor_addTestLines({{color = {r=R/4,g=0,b=0}, x+2,y-2, x-2,y+2 }})
					-- local x,y = (l1[1]+l2[1])/2,(l1[2]+l2[2])/2
					-- editor_addTestLines({{color = {r=0,g=R/4,b=0}, int[1],int[2], x,y }})
					-- editor_addTestLines({{color = {r=0,g=0,b=R/4}, x-1,y-1, x+1,y+1 }})
					-- editor_addTestLines({{color = {r=0,g=0,b=R/4}, x+1,y-1, x-1,y+1 }})
					-- end
					-- print("INNNNNNNNNNNNNNNNNNNNNNN")
					n = n + 1
					new[n][1], new[n][2] = int[1], int[2]
					table.remove(ints, i)
				end
				n = n + 1
				new[n][1], new[n][2] = l2[1], l2[2]
			end
		end
		-- cleanupStrokeLine(fill.line[f])
		-- table.remove(fill.line,f)
		-- new[0][0] = n
		-- table.insert(fill.line,f,new)
	end
	print("check1")
	-- for i,int in ipairs(ints) do
	--   local x,y,s = int[1],int[2],0.5
	--   editor_addTestLines({{color = {r=1,g=0,b=1}, x-s,y-s, x+s,y+s }})
	--   editor_addTestLines({{color = {r=1,g=0,b=1}, x+s,y-s, x-s,y+s }})
	-- end
end

local function booleanOperation(panel, sourceShape, destShapes, operation, debug)
	if #destShapes == 0 and not lines then
		return
	end
	local debug = true
	local lines = operation == "erase_lines"
	local operation = operation == "erase_lines" and "erase" or operation
	_start_timer("bool ops " .. operation)
	_time()

	local point, point2, point3 = {}, {}, {}
	POINT_IN_OUTER_TIME = 0
	POINT_IN_HOLES_TIME = 0
	local output
	if operation == "erase" then -------------------------------------------------------
		output = {}
		if lines then
			print("ERASE LINES")
			panel.selection = {}
			panel.selection.shape = {}
			for s = 1, sourceShape.line[1][0][0], 1 do
				local pt = sourceShape.line[1][s]
				table.insert(panel.selection.shape, pt[1])
				table.insert(panel.selection.shape, pt[2])
			end
			editor_selectStrokes(panel, true)
			editor_cutStrokeSelection(panel, true, true)
			panel.selection = {}
		end
		-- _disable_timers()
		local splitSegTime = 0
		if not sourceShape.bbox then
			print(inspect(sourceShape))
		end
		local box = ffi.new("float[5]", sourceShape.bbox)
		for d, shape in ipairs(destShapes) do
			box[3] = math.max(box[3], shape.bbox[3])
			box[1] = math.min(box[1], shape.bbox[1])
		end
		local w = box[3] - box[1]
		local h = box[4] - box[2]
		local offy = sourceShape.offy or 0
		local ox, ox1, ox2, ox3 = w, w, w * 2, w * 3
		local oy, oy1, oy2, oy3 = h + offy, h + offy, h * 2 + offy, h * 3 + offy
		local containing = false
		local line = sourceShape.line[1]
		point[1], point[2] = (line[1][1] + line[2][1]) / 2, (line[2][2] + line[2][2]) / 2
		-- table.remove(destShapes,1)
		_disable_timers()
		local intersections = {}
		for d, shape in ipairs(destShapes) do
			local newShapes = {}
			local operate = false
			local dw = (-#destShapes + d - 1) * w
			local line2 = shape.line[1]
			point2[1], point2[2] = (line2[1][1] + line2[2][1]) / 2, (line2[2][2] + line2[2][2]) / 2

			sourceShape.index, shape.index = 1, 2

			_tick("split segs")
			-- _enable_timers()
			-- _time() _disable_timers()
			-- (strokes,trim,simple,intonly,segregate,fills)
			local segs, bbbox, ints = fill_splitSegments({ sourceShape, shape }, false, true, false, true, true)
			for i, int in ipairs(ints) do
				table.insert(intersections, int)
			end
			-- _enable_timers() _time(segs[0][0])
			-- _disable_timers()
			_tock("split segs")

			local bbox = { bbbox.l, bbbox.t, bbbox.r, bbbox.b }
			local cutBox = ffi.new("float[5]", 0, bbbox.l, bbbox.t, bbbox.r, bbbox.b)
			local cutColor = lume.clone(shape.color)

			if debug then
				for s = 1, segs[0][0], 1 do
					local seg = segs[s]
					-- if seg[10]==1 then
					fill_addTestLines({
						{
							color = (seg[10] == sourceShape.index) and purple or blue,
							seg[1] + dw,
							seg[2] + oy,
							seg[3] + dw,
							seg[4] + oy,
						},
					})
					-- end
				end
			end

			if #ints == 0 then
				-- print("INTS 0 " )
				-- 1. if source is contained by a dest:
				-- add source[1] as a hole to dest shape
				-- create new fills with source[2,3,4...]
				if fill_pointInFill(panel, shape, point) then
					-- TODO add containing bbox check
					-- print("insert source as hole")
					local newShape = {}
					for l, line in ipairs(shape.line) do
						table.insert(newShape, line)
					end
					table.insert(newShape, sourceShape.line[1])
					table.insert(newShapes, newShape)
					shape.remove = true
				end

				-- 2. if dest is contained in source just throw it away, and stop checking for 1.
				if fill_pointInFill(panel, sourceShape, point2) then
					-- TODO add containing bbox check
					shape.remove = true
				end
			elseif #ints > 0 then
				_tick("build list")
				for s = segs[0][0], 1, -1 do
					if s <= segs[0][0] then
						local seg = segs[s]
						point2[1], point2[2] = (seg[1] + seg[3]) / 2, (seg[2] + seg[4]) / 2

						-- kill source segments outside dest shapes
						if seg[10] == sourceShape.index then
							if fill_pointInFill(panel, shape, point2) then
								shape.remove = true
								operate = true
								if debug then
									fill_addTestLines({ { color = green, seg[1] + dw, seg[2] + oy, seg[3] + dw, seg[4] + oy } })
								end
							else
								if debug then
									fill_addTestLines({ { color = orange, seg[1] + dw, seg[2] + oy, seg[3] + dw, seg[4] + oy } })
								end
								fill_copyFillSeg(segs[segs[0][0]], seg)
								segs[0][0] = segs[0][0] - 1
							end

						-- kill dest segments in source shape
						else
							if fill_pointInFill(panel, sourceShape, point2) then
								if debug then
									fill_addTestLines({ { color = red, seg[1] + dw, seg[2] + oy, seg[3] + dw, seg[4] + oy } })
								end
								operate = true
								fill_copyFillSeg(segs[segs[0][0]], seg)
								segs[0][0] = segs[0][0] - 1
							end
						end
					end
				end

				_tock("build list")

				_tick("split shapes")
				if operate then
					-- if debug then
					--   for s=1,segs[0][0],1 do
					--     local seg = segs[s]
					--     editor_addTestLines({{color=green,seg[1]+dw,seg[2]+oy2,seg[3]+dw,seg[4]+oy2}})
					--   end
					-- end

					local loosies = joinSegs(segs, cutBox, 0)
					debugLoosies(loosies, debug, 0, -offy)
					calcBoxesAndLargest(loosies)
					loosiesToShapes(loosies, newShapes)
				end
				_tock("split shapes")
			end

			if not shape.remove then
				table.insert(output, shape)
			end

			for f, fill in ipairs(newShapes) do
				-- print("shape",f,"with",#fill-1,"holes")
				local box = fill[1][0]
				local new = newStroke({
					group = "fill",
					bbox = ffi.new("float[5]", { box[2], box[3], box[4], box[5], box[6] }),
					color = cutColor,
					line = fill,
				})
				table.insert(panel.f.strokes, new)
				table.insert(panel.strokeDiffs.added, { frame = panel.f, stroke = panel.f.strokes[#panel.f.strokes] })
				table.insert(output, panel.f.strokes[#panel.f.strokes])
			end
		end

		_enable_timers()
		if not sourceShape.remove then
			-- splitFillWithInts(sourceShape,intersections)
			print("check2")
		end
		removeOperatedStrokes(panel)

		_tell_time("split segs")
		_tell_time(" seg checks")
		_tell_time("  sweeppoints")
		_tell_time("  sortpoints")
		_tell_time("  dochecks")
		_tell_time("build list")
		_tell_time("split shapes")
	end

	if operation == "combine" then -----------------------------------------------------
		local operate = false
		local toJoin = {}
		local strokes = lume.clone(destShapes)

		if sourceShape then
			table.insert(strokes, sourceShape)
		end

		for s, stroke in ipairs(strokes) do
			stroke.index = s
		end

		-- (strokes,trim,simple, intonly,segregate, fills)
		-- _disable_timers()
		local segs, bbbox, ints, uncut = fill_splitSegments(strokes, false, true, false, true, true)
		-- _enable_timers()
		bbox = { bbbox.l, bbbox.t, bbbox.r, bbbox.b }
		local w = bbbox.r - bbbox.l
		ox, ox2, ox3 = w, w * 2, w * 3

		if debug then
			local cols = {}
			for d = 1, #destShapes * 2, 1 do
				table.insert(cols, { r = math.random(), g = 0.5, b = math.random() })
			end
			for s = 1, segs[0][0], 1 do
				local seg = segs[s]
				local o = math.random() --(seg[10]/#destShapes)--+math.random()
				fill_addTestLines({
					{
						color = seg[21] == 2 and red or cols[seg[10]], --seg[21]==1 and red or blue,--,
						seg[1] + 500 + o,
						seg[2] + o,
						seg[3] + 500 + o,
						seg[4] + o,
					},
				})
			end
		end

		local cutShapesHash = {}
		for s = uncut + 1, segs[0][0], 1 do
			local seg = segs[s]
			if not cutShapesHash[seg[10]] then
				cutShapesHash[seg[10]] = true
			end
		end
		local cutShapesArray = {}
		local uncutShapesArray = {}
		for s, shape in ipairs(destShapes) do
			table.insert(cutShapesHash[shape.index] and cutShapesArray or uncutShapesArray, shape)
		end

		if sourceShape then
			-- source shape is either completely inside a dest or outside all dests
			local line = sourceShape.line[1]
			point[1], point[2] = (line[1][1] + line[2][1]) / 2, (line[2][2] + line[2][2]) / 2
			print("REEEEEEEEEEEEEEEEEEMOVE source1", #uncutShapesArray)
			for s, shape in ipairs(uncutShapesArray) do
				print("REEEEEEEEEEEEEEEEEEMOVE source2")
				if fill_pointInFill(panel, shape, point) then
					print("REEEEEEEEEEEEEEEEEEMOVE source3")
					sourceShape.remove = true
					shape.tris = nil
					-- otherwise, remove hole(s!) contained by source
					for h = #shape.line, 2, -1 do
						local hole = shape.line[h]
						point3[1], point3[2] = (hole[1][1] + hole[2][1]) / 2, (hole[2][2] + hole[2][2]) / 2
						if fill_pointInFill(panel, sourceShape, point3) then
							table.remove(shape.line, h)
						end
					end
					-- then add holes from source
					for h = 2, #sourceShape.line do
						local hole = sourceShape.line[h]
						table.insert(shape.line, hole)
					end
				end
				local line2 = shape.line[1]
				point2[1], point2[2] = (line2[1][1] + line2[2][1]) / 2, (line2[2][2] + line2[2][2]) / 2
				if fill_pointInFill(panel, sourceShape, point2) then
					shape.remove = true
				end
			end

			removeOperatedStrokes(panel, sourceShape)

			-- source shape is overlapping with dest
			if #ints > 0 then
				for s = segs[0][0], 1, -1 do
					if s <= segs[0][0] then
						local seg = segs[s]
						point[1], point[2] = (seg[1] + seg[3]) / 2, (seg[2] + seg[4]) / 2

						if seg[10] == sourceShape.index then
							-- kill source segments in dest shape
							for sh, shape in ipairs(cutShapesArray) do
								if fill_pointInFill(panel, shape, point) then
									toJoin[shape.index] = true
									toJoin[sourceShape.index] = true
									shape.remove = true
									sourceShape.remove = true
									operate = true
									-- if debug then editor_addTestLines({{color=orange,seg[1]-ox2,seg[2],seg[3]-ox2,seg[4]}}) end
									fill_copyFillSeg(segs[segs[0][0]], seg)
									segs[0][0] = segs[0][0] - 1
									break
								end
							end
						else
							-- kill dest segments in source shape
							if fill_pointInFill(panel, sourceShape, point) then
								toJoin[sourceShape.index] = true
								sourceShape.remove = true
								operate = true
								-- if debug then editor_addTestLines({{color=red,seg[1]-ox2,seg[2],seg[3]-ox2,seg[4]}}) end
								fill_copyFillSeg(segs[segs[0][0]], seg)
								segs[0][0] = segs[0][0] - 1
							end
						end
					end
				end
			end
			print("shape with", #sourceShape.line - 1, "holes")
		end

		_time("build combine list")

		if sourceShape and not operate then
			table.insert(panel.strokeDiffs.added, { frame = panel.f, stroke = panel.f.strokes[#panel.f.strokes] })
			_end_timer("bool ops " .. operation)
			return
		end

		if #ints > 0 or not sourceShape then
			if sourceShape then
				for s = segs[0][0], 1, -1 do
					if not toJoin[segs[s][10]] then
						fill_copyFillSeg(segs[segs[0][0]], segs[s])
						segs[0][0] = segs[0][0] - 1
					end
				end
			end
			_time("filter")

			if sourceShape then
				removeOperatedStrokes(panel, sourceShape)
			end

			local loosies = joinSegs(segs, bbox, 0)
			debugLoosies(loosies, debug, 0, 500)
			calcBoxesAndLargest(loosies)

			print("combined #loosies", #loosies)
			if loosies[1] then
				local news = {}
				local newShapes = loosiesToShapes(loosies)

				for f, fill in ipairs(newShapes) do
					local box = fill[1][0]
					local new = newStroke({
						group = "fill",
						bbox = ffi.new("float[5]", { box[2], box[3], box[4], box[5], box[6] }),
						color = lume.clone(panel.fillColor),
						line = fill,
					})
					table.insert(news, new)
				end

				if sourceShape then
					table.insert(panel.f.strokes, news[1])
					table.insert(
						panel.strokeDiffs.added,
						{ frame = panel.f, stroke = panel.f.strokes[#panel.f.strokes] }
					)
				else
					output = news
				end
			end
		end
	end

	-- print("POINT_IN_OUTER_TIME",POINT_IN_OUTER_TIME*60)
	-- print("POINT_IN_HOLES_TIME",POINT_IN_HOLES_TIME*60)

	_end_timer("bool ops " .. operation)
	return output
end

function bool_vectorShapes(panel, sourceShapes, erase, mode)
	print("\n---------------------------------------\n")
	panel.cachedStrokes = {}
	local eCount, cCount = 0, 0
	local eraseLines = erase or mode == "over_all"
	local toErase = {}
	local eraseHash = {}
	local debug = panel.debugView and panel.debugBoolean

	for sh, source in ipairs(sourceShapes) do
		for s, stroke in ipairs(panel.f.strokes) do
			if
				source ~= stroke
				and stroke.group == "fill"
				and (erase or mode == "behind_all" or (not sameColor(stroke.color, source.color)))
			then
				-- print("samecolor",sameColor(stroke.color, source.color))
				-- if not sameColor(stroke.color, source.color) then
				--   print(" col1",inspect(stroke.color))
				--   print(" col2",inspect(source.color))
				-- end
				if not stroke.bbox then
					editor_strokeBounds(stroke)
				end
				if bboxCollide1(source.bbox, stroke.bbox) then
					eraseHash[stroke] = stroke
				end
			end
		end
	end

	for k, v in pairs(eraseHash) do
		table.insert(toErase, v)
	end

	eCount = #toErase
	print("eCount", eCount)
	-- print("not erase and mode==behind_all",not erase , mode=="behind_all")
	if not erase and mode == "behind_all" then
		-- combine erasing shapes into clustered temp erasers
		local erasers = booleanOperation(panel, false, toErase, "combine")
		print("eraser made? ", erasers ~= nil, erasers and #erasers or " :(")

		-- then use these to erase the sourceshape
		local splitSource = { sourceShapes[1] }
		if erasers then
			for e, eraser in ipairs(erasers) do
				if e > 0 then
					eraser.offy = (e - 1) * 500
					-- print("splitSource?",splitSource)
					if splitSource then
						local booled = booleanOperation(panel, eraser, splitSource, "erase")
						-- print("booled?",booled)
						if booled then
							-- print("split to ", #booled)
							splitSource = booled
						end
					end
				end
			end
			sourceShapes = {}
		end
		local splitHash = {}
		for s, split in ipairs(splitSource) do
			splitHash[split.id] = split
		end
		-- print("hash",inspect(splitHash))

		local toCombine = {}
		for s = 1, #splitSource, 1 do
			local source = splitSource[s]
			for s, stroke in ipairs(panel.f.strokes) do
				if
					stroke.group == "fill"
					and source ~= stroke
					and not splitHash[stroke.id]
					and sameColor(stroke.color, source.color)
					and bboxCollide1(source.bbox, stroke.bbox)
				then
					-- print("add stroke",stroke.id)
					table.insert(splitSource, stroke)
					splitHash[stroke.id] = stroke
				end
			end
		end

		-- print("combining",inspect(splitSource))
		local combined = booleanOperation(panel, false, splitSource, "combine", true)

		-- print("new combis made")
		if combined then
			for s = #panel.f.strokes, 1, -1 do
				if splitHash[panel.f.strokes[s].id] then
					table.remove(panel.f.strokes, s)
				end
			end

			for c, comb in ipairs(combined) do
				table.insert(panel.f.strokes, comb)
			end
		end

	-- TODO
	-- the shapes that have just been split from source
	-- can be mutually excluded from another combine test
	else
		if eCount > 0 then
			if #sourceShapes == 1 then
				sourceShapes[1].offy = 0
				booleanOperation(panel, sourceShapes[1], toErase, "erase_lines")
			end
		end

		for sh, source in ipairs(sourceShapes) do
			if not erase then
				local toCombine = {}
				for s, stroke in ipairs(panel.f.strokes) do
					if
						stroke.group == "fill"
						and source ~= stroke
						and sameColor(stroke.color, source.color)
						and bboxCollide1(source.bbox, stroke.bbox)
					then
						-- print("samecolor2")
						-- if sameColor(stroke.color, source.color) then
						--   print(" col3",inspect(stroke.color))
						--   print(" col4",inspect(source.color))
						-- end
						table.insert(toCombine, stroke)
					end
				end
				cCount = #toCombine
				print("\nccount", cCount)
				source.offy = (sh - 2) * 500
				booleanOperation(panel, source, toCombine, "combine")
			end
		end
	end

	if (eCount + cCount) == 0 then
		if not erase then
			table.insert(panel.strokeDiffs.added, {
				frame = panel.f,
				stroke = panel.f.strokes[#panel.f.strokes],
			})
			return
		end
	end
end

function bool_lineToFill(panel, stroke, brushErase)
	local filltime = engine_getTime()
	print("\n-----------------------------------------------")
	_start_timer("Convert Line to Filled Stroke")

	-- debugFinal = panel.debugView and panel.debugBrush

	brushDebug = not debugFinal and (panel.debugView and panel.debugBrush)

	if stroke ~= panel.debugBrushStroke then
		panel.debugShapes = copyStrokes(panel.f.strokes)
		panel.debugBrushStroke = copyStroke(panel.stroke)
	end

	print("stroke copied")

	-- stroke.size = 10

	editor_strokeBounds(stroke, stroke.size / 2 + 5)
	local w, h = stroke.bbox[3] - stroke.bbox[1], stroke.bbox[4] - stroke.bbox[2]
	local doff = w + 10
	local doffx1, doffx2, doffx3, doffx4 = doff * 4, doff * 3, doff * 2, doff * 1

	_time()

	-- clear previous shit from the stage so i dont have to erase each time
	if panel.debugView then
		for s = #panel.f.strokes, 1, -1 do
			if panel.f.strokes[s].debug then --or (brushDebug and panel.f.strokes[s].group=="fill") then
				table.remove(panel.f.strokes, s)
			end
		end
	end

	table.remove(panel.f.strokes, #panel.f.strokes)

	-- get stroke outline
	local lineIn = stroke.line
	local strokeOut = {
		line = {},
		size = stroke.size, -- *panel.l.lineWidth
		group = "line",
		color = { r = 0, g = 0, b = 0, a = 1 },
	}
	local lineOut = strokeOut.line
	local segs, addFillSeg = fill_getFillSegmentReferences()

	-- stroke.size = stroke.size*0.9
	segs[0][0] = 0

	local verts, count = vector_renderStrokeOut(stroke, panel.l, panel.f, 2, segs, true)
	print("line length", stroke.line[0][0])
	print("vcount", count)
	count = count + 3
	_time("render")

	-- store verts of preview mesh in array of tris and sort by miny
	local numtris = count / 3
	local triangles = ffi.new(VECTOR_TYPE .. "[?][12]", numtris + 2)
	local compTris = ffi.cast("int (*)(float *, float *)", function(a, b)
		return a[8] - b[8]
	end)

	for v = 0, count - 3, 3 do
		local tri = triangles[v / 3]
		tri[1], tri[2] = verts[v].x, verts[v].y
		tri[3], tri[4] = verts[v + 1].x, verts[v + 1].y
		tri[5], tri[6] = verts[v + 2].x, verts[v + 2].y
		tri[7], tri[8] = math.min(tri[1], tri[3], tri[5]), math.min(tri[2], tri[4], tri[6])
		tri[9], tri[10] = math.max(tri[1], tri[3], tri[5]), math.max(tri[2], tri[4], tri[6])

		local d1, d2, d3 =
			math.dist(tri[1], tri[2], tri[3], tri[4]),
			math.dist(tri[5], tri[6], tri[3], tri[4]),
			math.dist(tri[1], tri[2], tri[5], tri[6])
		tri[11] = (d1 == 0 or d2 == 0 or d3 == 0) and 0 or triangleArea(d1, d2, d3)

		if brushDebug then
			fill_addTestLines({
				{
					color = purple,
					tri[1] - doffx1,
					tri[2],
					tri[3] - doffx1,
					tri[4],
					tri[5] - doffx1,
					tri[6],
					tri[1] - doffx1,
					tri[2],
				},
			})
		end
	end

	_time("triangles")

	-- ffi.C.qsort(triangles,numtris-1,ffi.sizeof(VECTOR_TYPE.."[12]"),compTris)

	-- intersect outline and break apart into segments
	-- _disable_timers()
	local segments, bbox = fill_splitSegments(segs, false, true)
	-- _enable_timers()
	_time("split segs")

	if not segments then
		editor_tempTip("brush failed")
		print("no splitsegs")
		return
	end

	_start_timer("create brush stroke outline")

	-- bbox.l,bbox.t,bbox.r,bbox.b = bbox.l-10,bbox.t-10,bbox.r+10,bbox.b+10
	-- update seg miny/maxy
	local segN = segments[0][0]
	if brushDebug then
		local offset = doffx2
		for s = 1, segments[0][0], 1 do
			local seg = segments[s]
			-- print(seg[1],seg[2],seg[3],seg[4])
			fill_addTestLines({
				{
					seg[1] - offset + math.random(),
					seg[2] + math.random(),
					seg[3] - offset + math.random(),
					seg[4] + math.random(),
					seg[1] - offset + math.random(),
					seg[2] + math.random(),
					seg[3] - offset + math.random(),
					seg[4] + math.random(),
				},
			})
		end
	end

	-- grids
	local bbox = stroke.bbox
	local tw, th = w / gridw, h / gridh

	-- triangle grid to speed up seg erasing
	local tgrid = ffi.new(string.format("u16[%i][%i][%i]", gridw, gridh, numtris))
	for i = 0, numtris - 1, 1 do
		local tri = triangles[i]
		local minx, miny, maxx, maxy = tri[7], tri[8], tri[9], tri[10]
		for y = 0, gridh - 1, 1 do
			for x = 0, gridw - 1, 1 do
				local l, t, r, b = bbox[1] + x * tw, bbox[2] + y * th, bbox[1] + (x + 1) * tw, bbox[2] + (y + 1) * th
				if minx - 1 < r and miny - 1 < b and maxx + 1 > l and maxy + 1 > t then
					tgrid[y][x][0] = tgrid[y][x][0] + 1
					tgrid[y][x][tgrid[y][x][0]] = i
				end
			end
		end
	end
	_time("grid")

	-- show all split segments
	if brushDebug then
		fill_addTestLines({
			{
				color = blue,
				bbox[1] - doffx1,
				bbox[2],
				bbox[1] + w - doffx1,
				bbox[2],
				bbox[1] + w - doffx1,
				bbox[2] + h,
				bbox[1] - doffx1,
				bbox[2] + h,
				bbox[1] - doffx1,
				bbox[2],
			},
		})
		for y = 0, gridh - 1, 1 do
			for x = 0, gridw - 1, 1 do
				fill_addTestLines({
					{
						color = lightblue,
						bbox[1] + x * tw - doffx1,
						bbox[2] + y * th,
						bbox[1] + (x + 1) * tw - doffx1,
						bbox[2] + y * th,
						bbox[1] + (x + 1) * tw - doffx1,
						bbox[2] + (y + 1) * th,
						bbox[1] + x * tw - doffx1,
						bbox[2] + (y + 1) * th,
					},
				})
			end
		end
		for y = 0, gridh - 1, 1 do
			local str = ""
			for x = 0, gridw - 1, 1 do
				str = str .. string.format("%03d", tgrid[y][x][0]) .. ","
			end
			print(str)
		end
		_time()
	end

	-- sort segs by miny
	local segSort = function(a, b)
		return a.z < b.z
	end
	local compInts = function(a, b)
		return a.x < b.x
	end

	-- ffi.C.qsort(segments,segN,ffi.sizeof(VECTOR_TYPE.."[24]"),compSegs)

	-- erase any segment with one point in a triangle
	local newSegments = ffi.new(SEGMENT_TYPE, mu(segN * 2) + 1)
	local newSegN = 0
	local hits = {}
	local samples = 12

	local x1, y1, x2, y2, x3, y3 = 2, 2, 100, 100
	local t = math.abs(math.sin(time))
	x3, y3 = lerp(x1, x2, t), lerp(y1, y2, t)
	for s = 1, segN, 1 do
		local seg = segments[s]
		-- if brushDebug then
		--  editor_addTestLines({{
		--    seg[1]-doffx2,seg[2],seg[3]-doffx2,seg[4],
		--    seg[1]-doffx2,seg[2],seg[3]-doffx2,seg[4]
		--  }})
		-- end
		local left, right, remove = false, false, 0
		for i = 1, samples, 1 do
			hits[i] = false
		end

		-- if either vert of seg is in a triangle, remove
		local mx, my = (seg[1] + seg[3]) / 2, (seg[2] + seg[4]) / 2
		local gx, gy = mf((mx - bbox[1]) / tw), mf((my - bbox[2]) / th)
		local cutter1, cutter2
		for i = 1, tgrid[gy][gx][0], 1 do
			local t = tgrid[gy][gx][i]
			local tri = triangles[t]
			if pointInTri(tri, mx, my, 0.01) then
				if not pointOnTri(tri, mx, my) then
					remove = 1
					break
				end
			end
		end

		if remove == 0 then
			newSegN = newSegN + 1
			newSegments[newSegN] = seg
		end
		if brushDebug then
			fill_addTestLines({
				{
					color = remove == 1 and red or (remove == 2 and orange or green),
					seg[1] - doffx3,
					seg[2],
					seg[3] - doffx3,
					seg[4],
					seg[1] - doffx3,
					seg[2],
				},
			})
		end
	end
	_time("erase inner")

	local segs = newSegments
	segs[0][0] = newSegN

	if segs[0][0] < 3 then
		print("brush stroke failed :( :( :( :( :( \n")
		return
	end

	local loosies = joinSegs(segs, bbox, 0.001, brushDebug)
	_time("convert to line")

	if #loosies == 0 then
		print("brush stroke failed :( :( :( :( :( \n")
		return
	end

	-- trim little dangling loops
	-- for l,loose in ipairs(loosies) do
	--  local new
	--  for i=loose[0][0],1,-1 do
	--    local j = wrap2(i+2,1,loose[0][0])
	--    local pt1 = loose[i]
	--    local pt2 = loose[j]
	--    if i~=j and math.max(math.abs(pt1[1]-pt2[1]),math.abs(pt1[2]-pt2[2]))<0.01 then
	--      copyFillSeg(loose[loose[0][0]],loose[i])
	--      loose[0][0] = loose[0][0]-1
	--      if brushDebug then
	--        editor_addTestLines({{color=red,
	--          pt1[1]-2,pt1[2]-2,
	--          pt1[1]+2,pt1[2]+2,
	--          pt1[1]-2,pt1[2]+2,
	--          pt1[1]+2,pt1[2]-2
	--        }})
	--      end
	--    end
	--    -- if then
	--    --  new =
	--    -- end
	--    -- if new then
	--    -- end
	--  end
	-- end
	print("succ", succ)

	-- calc size to found outer line
	if not calcBoxesAndLargest(loosies) then
		return
	end

	-- if bbox intersect, check that lines dont intersect, if they do, remove the smaller one
	-- replace with intonly splitseg?
	-- local collides = 0
	-- for l1=#loosies,1,-1 do
	--  local loose1 = loosies[l1]
	--  if loose1 then
	--     for l2=1,l1,1 do
	--       local loose2 = loosies[l2]
	--       if l1~=l2 and bboxCollide1(bboxes[loose1[0][1]],bboxes[loose2[0][1]]) then
	--         collides = collides+1
	--         for i=1, loose1[0][0],1 do
	--          local p1,p2 = loose1[i],loose1[wrap2(i+1,1,loose1[0][0])]
	--           for j=1, loose2[0][0],1 do
	--             local p3,p4 = loose2[i],loose2[wrap2(i+1,1,loose2[0][0])]
	--          local x,y = intersect(p1x,p1y,p2x,p2y, p3x,p3y,p4x,p4y)
	--          if x then
	--            local toRemove = loose1[0][2]>loose2[0][2] and l2 or l1
	--            print("remove",toRemove)
	--            table.remove(loosies,toRemove)
	--            goto done
	--          end
	--        end
	--      end
	--       end
	--     end
	--   end
	--  ::done::
	-- end
	-- print("collides",collides)

	if brushDebug or debugFinal then
		for l, loose in pairs(loosies) do
			local ox, oy = 0, 0
			local r, g, b = hsvToRgb(math.random(), 0.8, 0.7)
			local randomColor = { r = r, g = g, b = b, a = 1 }
			local test = { color = randomColor }
			local s = 0
			for i = 1, loose[0][0], 1 do
				local ox, oy = math.random() * s, math.random() * s
				table.insert(test, loose[i][1] + ox)
				table.insert(test, loose[i][2] + oy)
			end
			table.insert(test, loose[1][1] + ox)
			table.insert(test, loose[1][2] + oy)
			fill_addTestLines({ test })
		end
	end

	--  if loosies[2] then
	--  print("Hole Length1",loosies[2][0][0])
	-- end

	_end_timer("create brush stroke outline")

	-- actually add fill shape
	if #loosies > 0 and not (brushDebug or debugFinal) then
		for l, loose in ipairs(loosies) do
			loosies[l] = newStrokeLineFromCdata(loose)
			-- for i=1,loosies[l][0][0],1 do
			--  ax,ay,bx,by  = editor_bezierControlPoint(
			--  line[l-2][1],line[l-2][2],
			--  line[l-1][1],line[l-1][2],
			--  line[l][1],  line[l][2]
			-- )

			-- line[l-1][3],line[l-1][4] = ax,ay
			-- line[l-1][5],line[l-1][6] = bx,by
			-- end
		end
		table.insert(
			panel.f.strokes,
			newStroke({
				group = "fill",
				bbox = stroke.bbox,
				color = lume.clone(
					panel.debugRandomColor and { r = math.random() * 0.7, g = math.random() * 0.7, b = math.random() * 0.7, a = 0.7 }
						or stroke.color
				),
				line = loosies,
			})
		)

		_time("brush outline")
		local new = panel.f.strokes[#panel.f.strokes]

		local combined = bool_vectorShapes(panel, { new }, brushErase, panel.brushMode)

		if brushErase then
			lume.remove(panel.f.strokes, new)
		end

		-- if panel.debugView and panel.debugBoolean then
		--  table.remove(panel.f.strokes,#panel.f.strokes)
		-- end
	end

	_end_timer("Convert Line to Filled Stroke")
	print("-----------------------------------------------\n")
	panel.strokePreview = nil
end
