POINT_IN_OUTER_TIME = 0
POINT_IN_HOLES_TIME = 0
local sdist = function(x1, y1, x2, y2)
	return ((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1))
end
local dist = function(x1, y1, x2, y2)
	return ((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)) ^ 0.5
end
local abs = math.abs
local sign = math.sign
local min, max, sqrt = math.min, math.max, math.sqrt
local minN, maxN = math.min, math.max
local segbufsize = 2 ^ 16
local segmentBuf = ffi.new(SEGMENT_TYPE, segbufsize)
local splitSegs = ffi.new(SEGMENT_TYPE, segbufsize * 2)
local tempSeg = ffi.new(VECTOR_TYPE .. "[24]")
local strokeSeg = {}
local tmp = ffi.new(VECTOR_TYPE .. "[24]")
local tmpCurve = ffi.new(VECTOR_TYPE .. "[24]")
local showHullTriangles
local t1l, t1r = ffi.new("HullTriangle"), ffi.new("HullTriangle")
local t2l, t2r = ffi.new("HullTriangle"), ffi.new("HullTriangle")
local bezTests = 0
local col = {}
segmentBuf[0][1] = segbufsize
splitSegs[0][1] = segbufsize * 2

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

local function max9(a, b, c, d, e, f, g, h, i)
	return max(max(max(max(max(max(max(max(a, b), c), d), e), f), g), h), i)
end
local function min2(a, b)
	return math.min(a, b)
end
local function min4(a, b, c, d)
	return min(min(min(a, b), c), d)
end

local function min9(a, b, c, d, e, f, g, h, i)
	return min(min(min(min(min(min(min(min(a, b), c), d), e), f), g), h), i)
end
local function clockwise(line)
	local sum = 0
	print("line length line[0][0]", line[0][1])
	for l = 1, line[0][0] - 1, 1 do
		-- sum=sum + (line[l+1][1]-line[l][1])*(line[l+1][2]-line[l][2])
		-- print(sum)
	end
	return sum > 0 and true or false, sum
end
local function intersect(L1X1, L1Y1, L1X2, L1Y2, L2X1, L2Y1, L2X2, L2Y2)
	if
		(L1X1 == L2X1 and L1Y1 == L2Y1)
		or (L1X2 == L2X1 and L1Y2 == L2Y1)
		or (L1X1 == L2X2 and L1Y1 == L2Y2)
		or (L1X2 == L2X2 and L1Y2 == L2Y2)
	then
		return false
	end
	local d = (L2Y2 - L2Y1) * (L1X2 - L1X1) - (L2X2 - L2X1) * (L1Y2 - L1Y1)
	if d == 0 then
		return false
	else
		local ua = ((L2X2 - L2X1) * (L1Y1 - L2Y1) - (L2Y2 - L2Y1) * (L1X1 - L2X1)) / d
		local ub = ((L1X2 - L1X1) * (L1Y1 - L2Y1) - (L1Y2 - L1Y1) * (L1X1 - L2X1)) / d

		if ua >= 0 and ua <= 1 and ub >= 0 and ub <= 1 then
			return L1X1 + (ua * (L1X2 - L1X1)), L1Y1 + (ua * (L1Y2 - L1Y1))
		end
		return false
	end
end

local function growSegBufs()
	if segmentBuf[0][0] > segmentBuf[0][1] - 2 then
		print("GROW SEGBUF", segmentBuf[0][0], segmentBuf[0][1])
		segbufsize = mf(segbufsize * 2)
		local new = ffi.new(SEGMENT_TYPE, segbufsize)
		ffi_copy(new, segmentBuf, SEGMENT_SIZE * (segmentBuf[0][1] + 1))
		segmentBuf = new
		segmentBuf[0][1] = segbufsize

		if splitSegs[0][1] < segbufsize * 2 then
			local new = ffi.new(SEGMENT_TYPE, segbufsize * 2)
			ffi_copy(new, splitSegs, SEGMENT_SIZE * (splitSegs[0][1] + 1))
			splitSegs = new
			splitSegs[0][1] = segbufsize * 2
		end
	end
end

local function growSplitBuf()
	if splitSegs[0][0] > splitSegs[0][1] - 2 then
		local newSize = splitSegs[0][1] * 2
		local new = ffi.new(SEGMENT_TYPE, newSize)
		ffi_copy(new, splitSegs, SEGMENT_SIZE * (splitSegs[0][1] + 1))
		splitSegs = new
		splitSegs[0][1] = newSize
	end
end

local function getNextSeg()
	segmentBuf[0][0] = segmentBuf[0][0] + 1
	growSegBufs()
	return segmentBuf[segmentBuf[0][0]]
end

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

local function trianglesIntersect(t0, t1)
	local normal0 = (t0.b.x - t0.a.x) * (t0.c.y - t0.a.y) - (t0.b.y - t0.a.y) * (t0.c.x - t0.a.x)
	local normal1 = (t1.b.x - t1.a.x) * (t1.c.y - t1.a.y) - (t1.b.y - t1.a.y) * (t1.c.x - t1.a.x)

	return not (
			cross(t1, t0.a, t0.b, normal0)
			or cross(t1, t0.b, t0.c, normal0)
			or cross(t1, t0.c, t0.a, normal0)
			or cross(t0, t1.a, t1.b, normal1)
			or cross(t0, t1.b, t1.c, normal1)
			or cross(t0, t1.c, t1.a, normal1)
		)
end

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

	tri1.a.x, tri1.a.y = seg[1], seg[2]
	tri1.b.x, tri1.b.y = seg[5], seg[6]
	tri1.c.x, tri1.c.y = seg[3], seg[4]
	if sign1 == sign2 then
		tri2.a.x, tri2.a.y = seg[5], seg[6]
		tri2.b.x, tri2.b.y = seg[7], seg[8]
		tri2.c.x, tri2.c.y = seg[3], seg[4]
	else
		tri2.a.x, tri2.a.y = seg[1], seg[2]
		tri2.b.x, tri2.b.y = seg[3], seg[4]
		tri2.c.x, tri2.c.y = seg[7], seg[8]
	end
	-- if showHullTriangles then
	-- 	editor_addTestLines({{color={r=0,g=0,b=1},tri1.a.x, tri1.a.y,tri1.b.x, tri1.b.y,tri1.c.x, tri1.c.y}})
	-- 	editor_addTestLines({{color={r=0,g=0,b=1},tri2.a.x, tri2.a.y,tri2.b.x, tri2.b.y,tri2.c.x, tri2.c.y}})
	-- end
end

local function intersectHulls(seg1, seg2)
	hullTriangles(seg1, t1l, t1r)
	hullTriangles(seg2, t2l, t2r)
	return
		trianglesIntersect(t1l, t2l)
			or trianglesIntersect(t1l, t2r)
			or trianglesIntersect(t1r, t2l)
			or trianglesIntersect(t1r, t2r)
end

local function intersectHullLine(hull, line)
	return
		intersect(line[1], line[2], line[3], line[4], hull[1], hull[2], hull[3], hull[4]) or intersect(
		line[1],
		line[2],
		line[3],
		line[4],
		hull[1],
		hull[2],
		hull[5],
		hull[6]
	) or intersect(line[1], line[2], line[3], line[4], hull[5], hull[6], hull[7], hull[8]) or intersect(
		line[1],
		line[2],
		line[3],
		line[4],
		hull[7],
		hull[8],
		hull[3],
		hull[4]
	)
end

local function intersectSegBoxes(seg1, seg2)
	return
seg1[14] <= seg2[16] and seg2[14] <= seg1[16] and seg1[15] <= seg2[17] and seg2[15] <= seg1[17]
end

local function segFlat(seg, factor)
	-- local ax,ay = seg[3]-seg[1],seg[4]-seg[2]
	-- local alen  = math.sqrt(ax*ax+ay*ay)
	--  			ax,ay = ax/alen,ay/alen
	-- local bx,by = seg[7]-seg[1],seg[8]-seg[2]
	-- local blen  = math.sqrt(bx*bx+by*by)
	--  			bx,by = bx/blen,by/blen
	-- local cx,cy = seg[5]-seg[1],seg[6]-seg[2]
	-- local clen  = math.sqrt(cx*cx+cy*cy)
	--  			cx,cy = cx/clen,cy/clen
	-- local dot1,dot2 = ax*bx+ay*by, ax*cx+ay*cy
	return
		-- dot1>factor and dot2>factor
		-- (seg[1]==seg[5] and seg[2]==seg[6] and seg[3]==seg[7] and seg[4]==seg[8]) or
		-- dist(seg[1],seg[2],seg[3],seg[4])<0.1 or
		(
			distPointToLineSegment2(seg[5], seg[6], seg[1], seg[2], seg[3], seg[4], true) < factor
			and distPointToLineSegment2(seg[7], seg[8], seg[1], seg[2], seg[3], seg[4], true) < factor
		)
end

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

	right[5], right[6] = left[3], left[4]

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

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

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

	left[14] = minN(left[1], left[3], left[5], left[7])
	left[15] = minN(left[2], left[4], left[6], left[8])
	left[16] = maxN(left[1], left[3], left[5], left[7])
	left[17] = maxN(left[2], left[4], left[6], left[8])
	left[18] = dist(left[1], left[2], left[3], left[4])

	right[14] = minN(right[1], right[3], right[5], right[7])
	right[15] = minN(right[2], right[4], right[6], right[8])
	right[16] = maxN(right[1], right[3], right[5], right[7])
	right[17] = maxN(right[2], right[4], right[6], right[8])
	right[18] = dist(right[1], right[2], right[3], right[4])

	left[10], left[11], left[21] = source[10], source[11], left[21]
	right[10], right[11], right[21] = source[10], source[11], right[21]
end

local function lerp(a, b, t)
	return (1 - t) * a + t * b
end

local function intersectBeziers(seg1, seg2, intersections, n, force)
	n[1] = n[1] + 1
	bezTests = bezTests + 1
	local len1, len2 = seg1[18] == seg1[18] and seg1[18] or 0, seg2[18] == seg2[18] and seg2[18] or 0
	-- print("intersect depth ",n[1],len1,len2)
	-- local ax,ay,bx,by  = seg1[3]-seg1[1], seg1[4]-seg1[2], seg2[3]-seg2[1],seg2[4]-seg2[2]
	-- ax,ay,bx,by = ax/len1,ay/len1,bx/len2,by/len2
	-- local nx,ny = -ay,ax
	-- local dot = ax*bx+ay*by
	-- --0.999--1-((1-math.abs(dot))/100)^2
	local dot = 1

	local sflat1, sflat2 = len1 < 1 or segFlat(seg1, dot), len2 < 1 or segFlat(seg2, dot)
	-- if n[3]	then
	-- 	print("do we ever get here",sflat1,sflat2)
	-- end
	if force ~= -1 and (force == 1 or (sflat1 and sflat2)) then
		-- if n[3]	then  print("LETS GOOOOO") end
		local x, y, t1, t2

		if len1 == 0 or len2 == 0 then
			if len1 == 0 then
				x, y, t1 = seg1[1], seg1[2], seg1[12]
				t2 = lerp(seg2[12], seg2[13], dist(seg2[1], seg2[2], x, y) / len2)
				-- print("len1 0",seg1[1],seg1[2],seg1[3],seg1[4],seg1[12],seg1[13])
			else
				x, y, t2 = seg2[1], seg2[2], seg2[12]
				t1 = lerp(seg1[12], seg1[13], dist(seg1[1], seg1[2], x, y) / len1)
				-- print("len2 0",seg2[1],seg2[2],seg2[3],seg2[4],seg2[12],seg2[13])
			end
			-- t1 = lerp(seg1[12],seg1[13],1/2)
			-- t2 = lerp(seg2[12],seg2[13],1/2)
		else
			x, y = intersect(seg1[1], seg1[2], seg1[3], seg1[4], seg2[1], seg2[2], seg2[3], seg2[4])
			if x then
				t1 = lerp(seg1[12], seg1[13], dist(seg1[1], seg1[2], x, y) / len1)
				t2 = lerp(seg2[12], seg2[13], dist(seg2[1], seg2[2], x, y) / len2)
			end
		end

		if t1 then
			table.insert(intersections, { x, y, t1, t2 })
		end
	else
		-- DEBUG
		-- if n[3] then
		-- 	print(seg1[1],seg1[2], seg1[5],seg1[6],seg1[7],seg1[8], seg1[3],seg1[4])
		-- 	print(seg2[1],seg2[2], seg2[5],seg2[6],seg2[7],seg2[8], seg2[3],seg2[4])
		-- 	showHullTriangles = true
		-- else
		-- 	showHullTriangles = false
		-- end

		if intersectSegBoxes(seg1, seg2) then
			-- if (sflat1 and intersectHullLine(seg2,seg1)) or (sflat2 and intersectHullLine(seg1,seg2)) or intersectHulls(seg1,seg2) then
			-- if n[3]	then  print("intersect hulls") end
			n[2] = n[2] + 1
			local a1, a2 = seg1
			local b1, b2 = seg2

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

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

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

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

local function intSort(a, b)
	if a[4] == b[4] then
		return a[3] < b[3]
	end
	return a[4] < b[4]
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 updateSegEnds(ende, seg)
	if ende[0] > 0 then
		ende[0] = ende[0] + 1
		ende[1] = seg[11] < ende[1] and seg[11] or ende[1]
		ende[2] = seg[11] > ende[2] and seg[11] or ende[2]
		if seg[11] == ende[1] and seg[12] < ende[3] then
			ende[3], ende[5], ende[6] = seg[13], seg[3], seg[4]
		end
		if seg[11] == ende[2] and seg[13] > ende[4] then
			ende[4], ende[7], ende[8] = seg[12], seg[1], seg[2]
		end
	else
		ende[0] = 1
		ende[1], ende[2], ende[3], ende[4], ende[5], ende[6], ende[7], ende[8] =
			seg[11], seg[11], seg[13], seg[12], seg[1], seg[2], seg[3], seg[4]
	end
end
local function updateSegBounds(seg)
	seg[14] = min4(seg[1], seg[3], seg[5], seg[7])
	seg[15] = min4(seg[2], seg[4], seg[6], seg[8])
	seg[16] = max4(seg[1], seg[3], seg[5], seg[7])
	seg[17] = max4(seg[2], seg[4], seg[6], seg[8])

	seg[18] = dist(seg[1], seg[2], seg[3], seg[4])
end
local function sweepSort(a, b)
	return a[2] == b[2] and a[1] < b[1] or a[2] < b[2]
end
local function getSegChecks(segments, force, segregate, fills)
	_tick("  sweeppoints")
	local sweepPoints = {}
	for i = 1, segments[0][0], 1 do
		--skip splitting fills unless explicitly to
		if fills or segments[i][20] == 0 then
			table.insert(sweepPoints, { 0, segments[i][14], i })
			table.insert(sweepPoints, { 1, segments[i][16], i })
		end
	end
	_tock("  sweeppoints")

	_tick("  sortpoints")
	table.sort(sweepPoints, sweepSort)
	_tock("  sortpoints")

	_tick("  dochecks")
	maxactive = 0
	local active = {}
	local checks = {}
	local ax, ay, bx, by
	for i = 1, #sweepPoints, 1 do
		local pt = sweepPoints[i]
		if pt[1] == 0 then
			for a = 1, #active, 1 do
				local seg1, seg2 = segments[pt[3]], segments[active[a]]
				local consecutive
				local sameStroke = seg1[10] == seg2[10]
				if (segregate and not sameStroke) or not segregate then
					if not force then
						consecutive = (seg1[10] == seg2[10] and (seg1[11] >= seg2[11] - 1 and seg1[11] <= seg2[11] + 1))
						if consecutive then
							ax, ay = (seg1[3] - seg1[1]) / seg1[18], (seg1[4] - seg1[2]) / seg1[18]
							bx, by = (seg2[3] - seg2[1]) / seg2[18], (seg2[4] - seg2[2]) / seg2[18]
							consecutive = (ax * bx + ay * by) > -0.5
						end
					end
					if active[a] ~= pt[3] and not consecutive and seg1[17] >= seg2[15] and seg1[15] <= seg2[17] then
						table.insert(checks, active[a])
						table.insert(checks, pt[3])
					end
				end
			end
			maxactive = math.max(maxactive, #active)
			table.insert(active, pt[3])
		else
			for a = 1, #active, 1 do
				if active[a] == pt[3] then
					table.remove(active, a)
					break
				end
			end
		end
	end
	_tock("  dochecks")
	-- print("intersection checks queued",#checks/2)
	return checks
end
local function close(segs, edge, segments)
	local seg
	for s = 1, #segs, 1 do
		seg = segments[segs[s]]
		local d1, d2 =
			distPointToLineSegment({ edge.p1.x, edge.p1.y }, { seg[1], seg[2] }, { seg[3], seg[4] }),
			distPointToLineSegment({ edge.p2.x, edge.p2.y }, { seg[1], seg[2] }, { seg[3], seg[4] })
		if d1 < 0.1 and d2 < 0.1 then
			return true
		end
	end
	return false
end
local function trit(t)
	return { t[1], t[2], t[3], t[4], t[5], t[6] }
end
local function getFills(strokes)
	local fills = {}
	for s, stroke in ipairs(strokes) do
		if stroke.group == "fill" then
			table.insert(fills, stroke)
		end
	end
	return fills
end
local function colorCheck(a, b, n, o)
	local m = 1
	return (math.abs(a.r - b.r) * m < n and math.abs(a.g - b.g) * m < n and math.abs(a.b - b.b) * m < n)
end
local function test(x, y)
	if not (x > 1 and y > 1 and x < animatePanel.fillDims.w - 1 and y < animatePanel.fillDims.h - 1) then
		return false
	end
	if animatePanel.fillGrid[y][x] == false then
		col.r, col.g, col.b, col.a = animatePanel.fillTestData:getPixel(x, y)
		if animatePanel.fillStart.a == 0 then
			return col.a <= math.min(animatePanel.fillTolerance, 0.95)
		else
			return colorCheck(col, animatePanel.fillStart, animatePanel.fillTolerance, false)
		end
	else
		return false
	end
end
local function runFloodFill(x, y, stack)
	local count = 0
	local data = animatePanel.fillTestData
	local ptr = ffi.cast("uint8_t*", data.data.data)
	local tolerance = math.min(animatePanel.fillTolerance, 0.99) * 255
	local alphaCheck = animatePanel.fillStart.a == 0
	for i = 0, panel.fillDims.h - 1, 1 do
		for j = 0, panel.fillDims.w - 1, 1 do
			local col = ffi.cast("unsigned char *", ptr)
			if col[3] > 0 then
				if alphaCheck then
					if col[3] > tolerance then
						panel.fillGrid[i][j] = true
						count = count + 1
					end
				else
					panel.fillGrid[i][j] = true
				end
			end
			ptr = ptr + data.stride
		end
	end
	print("ref", count, math.sqrt(count))

	ffi_copy(panel.fillRef, panel.fillGrid, panel.fillDataSize)

	if not panel.fillGrid[y][x] then
		fill_rasterFill(panel.fillGrid, x, y, panel.fillDims.w, panel.fillDims.h)
	end

	local count = 0
	local data = animatePanel.filledData
	local ptr = ffi.cast("uint8_t*", data.data.data)
	for i = 0, panel.fillDims.h - 1, 1 do
		for j = 0, panel.fillDims.w - 1, 1 do
			if panel.fillGrid[i][j] and not panel.fillRef[i][j] then
				local col = ffi.cast("unsigned char *", ptr)
				col[0], col[1], col[2], col[3] = 255, 255, 255, 255
				count = count + 1
			end
			ptr = ptr + data.stride
		end
	end

	print("filled", count, math.sqrt(count))
	return true
end

local function segmentStrokes(strokes, ends, fills)
	local min, max, sqrt = math.min, math.max, math.sqrt
	local segs = segmentBuf
	segs[0][0] = 0

	if not strokes[1] or not strokes[1].line[1] then
		return false
	end
	-- local numClose = 0
	-- for s=1,#strokes,1 do
	--  local stroke= strokes[s]
	--  if stroke.group == "line" and not stroke.debug and stroke.line and stroke.line[0] then
	--    local line = stroke.line
	--    for i = line[0][0]-1, 1,-1 do
	--      local close = math.abs(line[i][1]-line[i+1][1])<0.1 and math.abs(line[i][2]-line[i+1][2])<0.1
	--      if close then
	--        numClose= numClose+1
	--        print(line[i][1],line[i][2])
	--        -- print("TOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO")
	--        -- print("CLOOOOOOOOOOOOOOOOOOOOOOOOOOOOOSE")
	--      end
	--    end
	--  end
	-- end
	-- print("numClose",numClose)
	_time()
	local tick = 0
	local stime, dtime, mtime, btime = 0, 0, 0, 0
	local bbox = ffi.new("float[5]")

	for s = 1, #strokes, 1 do
		local stroke = strokes[s]
		if not stroke.debug and stroke.line and stroke.line[1] then
			local isFill = stroke.group == "fill" and 1 or 0
			local n = isFill == 1 and #stroke.line or 1
			local line
			for ln = 1, n, 1 do
				line = isFill == 1 and stroke.line[ln] or stroke.line
				local index = stroke.index or s
				local layer = stroke.layer or 0
				local last = (fills and isFill == 1) and line[0][0] or line[0][0] - 1
				ffi.new("float[5]")
				if bbox[0] == 0 then
					bbox[0] = 1
					bbox[1] = line[1][1]
					bbox[2] = line[1][2]
					bbox[3] = line[1][1]
					bbox[4] = line[1][2]
				end
				if ends then
					ends[s][0] = 0
				end

				ffi.C.segmentize(segs, line, bbox, isFill, last, index)
				growSegBufs()
			end
		end
	end

	local bbbox = { l = bbox[1], t = bbox[2], r = bbox[3], b = bbox[4] }

	_time("segment strokes")
	-- segmentBuf[0][1],segmentBuf[0][3] = 9999999,9999999
	if not bbbox then
		return
	end
	bbbox.w = (bbbox.r - bbbox.l)
	bbbox.h = (bbbox.b - bbbox.t)
	return segmentBuf, bbbox
end
local function cachedLine(panel, shape, force, detail)
	local cache = shape.group and panel.cachedStrokes or panel.cachedFills
	if force or not cache[shape.index] or (detail and cache[shape.index][0] ~= detail) then
		cache[shape.index] = {}

		bench.push("filloutline")
		-- print("bez cached line")
		cache[shape.index][0] = detail
		for l, line in ipairs(shape.line) do
			local bez = vector_bezierLine(shape, l, detail or 3, true)
			cache[shape.index][l] = ffi.new("float[?][6]", bez[0][0] + 2)
			ffi_copy(cache[shape.index][l], bez, (bez[0][0] + 1) * ffi.sizeof("float[6]"))
			cache[shape.index][l][0][0] = bez[0][0]
			cache[shape.index][l][0][1] = bez[0][0]
		end
		bench.pop("filloutline")

		-- table.remove(cache[shape.index],#cache[shape.index])
		-- table.remove(cache[shape.index],#cache[shape.index])
	end

	return cache[shape.index]
end

function fill_rasterFill(array, x, y, width, height)
	-- at this point, we know array[y,x] is clear, and we want to move as far as possible to the upper-left. moving
	-- up is much more important than moving left, so we could try to make this smarter by sometimes moving to
	-- the right if doing so would allow us to move further up, but it doesn't seem worth the complexity
	while true do
		local ox = x
		local oy = y
		while y ~= 0 and not array[y - 1][x] do
			y = y - 1
		end
		while x ~= 0 and not array[y][x - 1] do
			x = x - 1
		end
		if x == ox and y == oy then
			break
		end
	end
	fill_coreFill(array, x, y, width, height)
end

function fill_coreFill(array, x, y, width, height)
	-- at this point, we know that array[y,x] is clear, and array[y-1,x] and array[y,x-1] are set.
	-- we'll begin scanning down and to the right, attempting to fill an entire rectangular block
	local lastRowLength = 0 -- the number of cells that were clear in the last row we scanned
	repeat
		local rowLength = 0
		local sx = x -- keep track of how long this row is. sx is the starting x for the main scan below

		-- now we want to handle a case like |***|, where we fill 3 cells in the first row and then after we move to
		-- the second row we find the first  | **| cell is filled, ending our rectangular scan. rather than handling
		-- this via the recursion below, we'll increase the starting value of 'x' and reduce the last row length to
		-- match. then we'll continue trying to set the narrower rectangular block
		if lastRowLength ~= 0 and array[y][x] then -- if this is not the first row and the leftmost cell is filled...
			repeat
				lastRowLength = lastRowLength - 1
				if lastRowLength == 0 then
					return -- shorten the row. if it's full, we're done
				end
				x = x + 1
			until not array[y][x] -- otherwise, update the starting point of the main scan to match
			sx = x
		-- we also want to handle the opposite case, | **|, where we begin scanning a 2-wide rectangular block and
		-- then find on the next row that it has     |***| gotten wider on the left. again, we could handle this
		-- with recursion but we'd prefer to adjust x and lastRowLength instead
		else
			while x ~= 0 and not array[y][x - 1] do
				x = x - 1
				array[y][x] = true -- to avoid scanning the cells twice, we'll fill them and update rowLength here
				-- if there's something above the new starting point, handle that recursively. this deals with cases
				-- like |* **| when we begin filling from (2,0), move down to (2,1), and then move left to (0,1).
				-- the  |****| main scan assumes the portion of the previous row from x to x+lastRowLength has already
				-- been filled. adjusting x and lastRowLength breaks that assumption in this case, so we must fix it
				if y ~= 0 and not array[y - 1][x] then
					fill_rasterFill(array, x, y - 1, width, height) -- use _Fill since there may be more up and left
				end
				rowLength = rowLength + 1
				lastRowLength = lastRowLength + 1
			end
		end

		-- now at this point we can begin to scan the current row in the rectangular block. the span of the previous
		-- row from x (inclusive) to x+lastRowLength (exclusive) has already been filled, so we don't need to
		-- check it. so scan across to the right in the current row
		while sx < width and not array[y][sx] do
			array[y][sx] = true
			rowLength = rowLength + 1
			sx = sx + 1
		end
		-- now we've scanned this row. if the block is rectangular, then the previous row has already been scanned,
		-- so we don't need to look upwards and we're going to scan the next row in the next iteration so we don't
		-- need to look downwards. however, if the block is not rectangular, we may need to look upwards or rightwards
		-- for some portion of the row. if this row was shorter than the last row, we may need to look rightwards near
		-- the end, as in the case of |*****|, where the first row is 5 cells long and the second row is 3 cells long.
		-- we must look to the right  |*** *| of the single cell at the end of the second row, i.e. at (4,1)
		if rowLength < lastRowLength then
			local last = x + lastRowLength
			sx = sx + 1
			while sx < last do -- 'end' is the end of the previous row, so scan the current row to
				-- there. any clear cells would have been connected to the previous
				if not array[y][sx] then
					fill_coreFill(array, sx, y, width, height) -- row. the cells up and left must be set so use FillCore
				end
				sx = sx + 1
			end

		-- alternately, if this row is longer than the previous row, as in the case |*** *| then we must look above
		-- the end of the row, i.e at (4,0)                                         |*****|
		elseif rowLength > lastRowLength and y ~= 0 then -- if this row is longer and we're not already at the top...
			local ux = x + lastRowLength + 1
			while ux < sx do -- sx is the end of the current row
				if not array[y - 1][ux] then
					fill_rasterFill(array, ux, y - 1, width, height) -- since there may be clear cells up and left, use _Fill
				end
				ux = ux + 1
			end
		end
		lastRowLength = rowLength -- record the new row length
		y = y + 1
	until not (lastRowLength ~= 0 and y < height) -- if we get to a full row or to the bottom, we're done
end

function fill_sliceCurve(source, slice, a, b)
	tmp[1], tmp[2] = lerp(source[1], source[5], b), lerp(source[2], source[6], b) -- E
	tmp[3], tmp[4] = lerp(source[5], source[7], b), lerp(source[6], source[8], b) -- F
	tmp[5], tmp[6] = lerp(source[7], source[3], b), lerp(source[8], source[4], b) -- G
	tmp[7], tmp[8] = lerp(tmp[3], tmp[5], b), lerp(tmp[4], tmp[6], b) -- J

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

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

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

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

	slice[14] = minN(slice[1], slice[3], slice[5], slice[7])
	slice[15] = minN(slice[2], slice[4], slice[6], slice[8])
	slice[16] = maxN(slice[1], slice[3], slice[5], slice[7])
	slice[17] = maxN(slice[2], slice[4], slice[6], slice[8])
	slice[18] = dist(slice[1], slice[2], slice[3], slice[4])

	slice[21] = source[21]
end

function fill_copyFillSeg(from, to)
	to[1], to[2], to[3], to[4], to[5], to[6], to[7], to[8], to[9], to[10], to[11], to[12], to[13], to[14], to[15], to[16], to[17], to[18], to[19], to[20], to[21], to[22], to[23] =
		from[1],
		from[2],
		from[3],
		from[4],
		from[5],
		from[6],
		from[7],
		from[8],
		from[9],
		from[10],
		from[11],
		from[12],
		from[13],
		from[14],
		from[15],
		from[16],
		from[17],
		from[18],
		from[19],
		from[20],
		from[21],
		from[22],
		from[23]
end

function fill_getFillSegmentReferences()
	return segmentBuf, addFillSeg
end

function fill_splitSegments(strokes, trim, simple, intonly, segregate, fills, skipfilter)
	_start_timer("trim time")
	_time()

	collectgarbage("stop")
	local segs, bbox, ends

	if type(strokes) == "table" then
		ends = ffi.new(VECTOR_TYPE .. "[?][9]", #strokes + 1)
		segs, bbox = segmentStrokes(strokes, ends, fills)
		minN, maxN = math.min, math.max
	else
		minN, maxN = min2, max2
		segs = strokes
		bbox = {
			l = segs[0][1],
			t = segs[0][2],
			r = segs[0][1],
			b = segs[0][2],
		}
		for s = 1, segs[0][0], 1 do
			local seg = segs[s]
			if simple then
				seg[5], seg[6] = seg[1], seg[2]
				seg[7], seg[8] = seg[3], seg[4]
				seg[14] = math.min(seg[1], seg[3])
				seg[15] = math.min(seg[2], seg[4])
				seg[16] = math.max(seg[1], seg[3])
				seg[17] = math.max(seg[2], seg[4])
			end

			bbox.l = math.min(bbox.l, seg[1], seg[3])
			bbox.t = math.min(bbox.t, seg[2], seg[4])
			bbox.r = math.max(bbox.r, seg[1], seg[3])
			bbox.b = math.max(bbox.b, seg[2], seg[4])
		end
		_time("time2")
	end

	_time("segment")
	if not segs then
		print("no segs")
		return
	end

	-- remove duplicate fill edges
	ffi.C.qsort(ffi.cast("float*", segs) + 24, segs[0][0], ffi.sizeof("float[24]"), ffi.C.sortSegs)
	if fills and not skipfilter then
		for s1 = max(1, segs[0][0] - 32), 1, -1 do
			for s2 = min(segs[0][0], s1 + 32), s1 + 1, -1 do
				local seg1, seg2 = segs[s1], segs[s2]
				if
					s1 ~= s2
					and seg1[14] == seg2[14]
					and seg1[16] == seg2[16]
					and seg1[15] == seg2[15]
					and seg1[17] == seg2[17]
				then
					if seg2[20] == 1 then
						fill_copyFillSeg(segs[segs[0][0]], segs[s2])
						segs[0][0] = segs[0][0] - 1
					end
					if seg1[20] == 1 then
						fill_copyFillSeg(segs[segs[0][0]], segs[s1])
						segs[0][0] = segs[0][0] - 1
					end
				end
			end
		end
		_time("fdupes")
	else
		-- for s1=segs[0][0],1,-1 do
		-- 	local seg1 = segs[s1]
		-- 	local removal,line
		-- 	for s2=segs[0][0],1,-1 do
		-- 		local seg2 = segs[s2]
		-- 		if s1~=s2 and s1<segs[0][0] and s2<segs[0][0] and abs(seg1[1]-seg2[1])<0.1 and abs(seg1[2]-seg2[2])<0.1 and abs(seg1[3]-seg2[3])<0.1 and abs(seg1[4]-seg2[4])<0.1 then
		-- 			removal = segs[math.max(s1,s2)]
		-- 			copyFillSeg(segs[segs[0][0]],removal)
		-- 			segs[0][0]=segs[0][0]-1
		-- 			break
		-- 		end
		-- 	end
		-- end
		_time("dupes")
	end

	if segs[0][0] < 1 then
		print("no segs to split")
		return
	end

	if fills then
		-- remove semiduplicates (share one point and other point lies on longer seg)
		local matched = 0
		_time()
		for s1 = max(1, segs[0][0] - 32), 1, -1 do
			for s2 = min(segs[0][0], s1 + 32), s1 + 1, -1 do
				local seg1, seg2 = segs[s1], segs[s2]
				if s1 ~= s2 and s1 <= segs[0][0] and s2 <= segs[0][0] and seg1[10] ~= seg2[10] then -- and not (seg1[21]==1 and seg2[21]==1) then
					local s1ax, s1ay, s1bx, s1by = seg1[1], seg1[2], seg1[3], seg1[4]
					local s2ax, s2ay, s2bx, s2by = seg2[1], seg2[2], seg2[3], seg2[4]
					local aa = s1ax == s2ax and s1ay == s2ay
					local ab = s1ax == s2bx and s1ay == s2by
					local ba = s1bx == s2ax and s1by == s2ay
					local bb = s1bx == s2bx and s1by == s2by
					local any = aa or ab or ba or bb
					if any then
						local s1dx, s1dy, s2dx, s2dy = s1bx - s1ax, s1by - s1ay, s2bx - s2ax, s2by - s2ay
						local grad1, grad2 = s1dy / s1dx, s2dy / s2dx
						if
							abs(grad1 - grad2) < 0.1
							and (
								(aa or bb) and dotprod(s1ax, s1ay, s1bx, s1by, s2ax, s2ay, s2bx, s2by) > 0.9
								or dotprod(s1ax, s1ay, s1bx, s1by, s2ax, s2ay, s2bx, s2by) < -0.9
							)
						then
							seg1[21] = 1
							seg2[21] = 1
							local len1, len2 = s1dx * s1dx + s1dy * s1dy, s2dx * s2dx + s2dy * s2dy
							local longer = len1 > len2 and s1 or s2
							local shorter = len1 > len2 and s2 or s1
							-- editor_addTestLines({{
							--         color = seg1[21]==1 and {r=1,g=0,b=0} or {r=0,g=0,b=1},
							--         seg1[1]+1,seg1[2]+1,seg1[3]+1,seg1[4]+1
							--       }})
							-- editor_addTestLines({{
							--         color = seg2[21]==1 and {r=1,g=0,b=0} or {r=0,g=0,b=1},
							--         seg2[1]+2,seg2[2]+2,seg2[3]+2,seg2[4]+2
							--       }})

							local seg1 = segs[longer]
							local seg2 = segs[shorter]
							local s1ax, s1ay, s1bx, s1by = seg1[1], seg1[2], seg1[3], seg1[4]
							local s2ax, s2ay, s2bx, s2by = seg2[1], seg2[2], seg2[3], seg2[4]
							local aa = s1ax == s2ax and s1ay == s2ay
							local ab = s1ax == s2bx and s1ay == s2by
							local ba = s1bx == s2ax and s1by == s2ay
							local bb = s1bx == s2bx and s1by == s2by
							if aa then
								seg1[1], seg1[2] = seg2[3], seg2[4]
							elseif bb then
								seg1[3], seg1[4] = seg2[1], seg2[2]
							elseif ab then
								seg1[1], seg1[2] = seg2[1], seg2[2]
							elseif ba then
								seg1[3], seg1[4] = seg2[3], seg2[4]
							end
							fill_copyFillSeg(segs[segs[0][0]], segs[shorter])
							segs[0][0] = segs[0][0] - 1

							break
						end
					end
				end
			end
		end
		_time("remove danglies")
	end

	-- print("panel.debugView and panel.tool==fill", panel.debugView, panel.tool == "fill")
	-- if panel.debugView and panel.tool == "fill" then
	-- 	-- local dist = math.dist
	-- 	-- for s1=max(1,segs[0][0]-32),1,-1 do
	-- 	-- 	for s2=min(segs[0][0],s1+32),s1+1,-1 do
	-- 	-- 			local seg1,seg2 = segs[s1],segs[s2]
	-- 	-- 			if s1~=s2 and s1<=segs[0][0] and  s2<=segs[0][0] and seg1[10]~=seg2[10]  then
	-- 	-- 				local s1ax,s1ay,s1bx,s1by = seg1[1],seg1[2],seg1[3],seg1[4]
	-- 	-- 				local s2ax,s2ay,s2bx,s2by = seg2[1],seg2[2],seg2[3],seg2[4]
	-- 	-- 				local s1dx,s1dy,s2dx,s2dy = s1bx-s1ax,s1by-s1ay,s2bx-s2ax,s2by-s2ay
	-- 	-- 				local grad1,grad2 = s1dy/s1dx,s2dy/s2dx
	-- 	-- 				local len1,len2 = s1dx*s1dx+s1dy*s1dy,s2dx*s2dx+s2dy*s2dy
	-- 	-- 				if abs(abs(grad1)-abs(grad2))<0.001
	-- 	-- 					and len1+len2> math.max(
	-- 	-- 					dist(s1ax,s1ay,s2ax,s2ay),
	-- 	-- 					dist(s1ax,s1ay,s2bx,s2by),
	-- 	-- 					dist(s1bx,s1by,s2ax,s2ay),
	-- 	-- 					dist(s1bx,s1by,s2bx,s2by)
	-- 	-- 				)
	-- 	-- 					then

	-- 	--  				print("hey")
	-- 	-- 				editor_addTestLines({{
	-- 	-- 		      seg1[1]+math.random()*0.1,seg1[2]+math.random()*0.1,
	-- 	-- 		      seg1[3]+math.random()*0.1,seg1[4]+math.random()*0.1
	-- 	-- 		    }})
	-- 	-- 			  break
	-- 	-- 			end
	-- 	-- 		end
	-- 	-- 	end
	-- 	-- end
	-- 	for s = 1, segs[0][0], 1 do
	-- 		local seg = segs[s]
	-- 		local o = math.random()
	-- 		fill_addTestLines({
	-- 			{
	-- 				color = seg[10] == 1 and { r = 1, g = 0, b = 0 } or { r = 0, g = 0, b = 1 },
	-- 				seg[1] + o,
	-- 				seg[2] + o,
	-- 				seg[3] + o,
	-- 				seg[4] + o,
	-- 			},
	-- 		})
	-- 	end
	-- 	-- _time("extra")
	-- 	return false
	-- end

	if segs[0][0] < 1 then
		print("no segs to split")
		return
	end

	for s = 1, segs[0][0], 1 do
		local seg = segs[s]
		seg[9] = s
	end

	if not segs then
		return false
	end

	_tick(" seg checks")
	local checks = getSegChecks(segs, not ends, segregate, fills)
	_tock(" seg checks")

	_time("seg checks")

	-- local segPicked
	-- for s=1,segmentN,1 do
	-- 	local seg = segmentBuf[s]
	-- 	if distPointToLineSegment2(
	-- 			animatePanel.brushCoords.x,animatePanel.brushCoords.y,
	-- 				seg[1],seg[2],seg[3],seg[4])<0.1 then
	-- 		segPicked = s
	-- 		editor_addTestLines({{color={r=1,g=0,b=0},seg[1],seg[2], seg[5],seg[6],seg[7],seg[8], seg[3],seg[4]}})
	-- 	end
	-- end
	local startSegCount = segs[0][0]
	local insertTime = 0
	local bezTime = 0
	local segCuts = {}
	local segTests = 0
	local inters = 0
	bezTests = 0
	splitSegs[0][0] = 0
	local segInts
	local intersectionShown = false
	local count = {}
	local ints = 0
	local allInts = {}
	for c = 1, #checks - 1, 2 do
		local seg1 = segs[checks[c]]
		local seg2 = segs[checks[c + 1]]
		-- print("check",seg1[10],seg2[10])
		local s1, s2 = seg1[9], seg2[9]
		local intersections = {}
		segTests = segTests + 1

		-- DEBUG
		-- local showIntersection = (checks[c] == segPicked) or (checks[c+1]==segPicked)
		-- if not intersectionShown then
		-- 	if checks[c] == segPicked then
		-- 			editor_addTestLines({{seg2[1],seg2[2], seg2[3],seg2[4]}})
		-- 		elseif checks[c+1]==segPicked then
		-- 			editor_addTestLines({{seg1[1],seg1[2], seg1[3],seg1[4]}})
		-- 		end
		-- 	end

		count[1], count[2], count[3], count[4] = 0, 0, showIntersection and not intersectionShown, -1
		if simple then
			local x, y = intersect(seg1[1], seg1[2], seg1[3], seg1[4], seg2[1], seg2[2], seg2[3], seg2[4])
			if x then
				ints = ints + 1
				table.insert(
					intersections,
					{ x, y, dist(seg1[1], seg1[2], x, y) / seg1[18], dist(seg2[1], seg2[2], x, y) / seg2[18] }
				)
				table.insert(allInts, { x, y })
			end
		else
			intersectBeziers(seg1, seg2, intersections, count, -1)
			ints = ints + #intersections
		end

		-- if showIntersection then intersectionShown = true end
		-- if count[2]>0 then
		-- editor_addTestLines({{seg1[1],seg1[2],seg1[5],seg1[6],seg1[7],seg1[8],seg1[3],seg1[4]}})
		-- editor_addTestLines({{seg2[1],seg2[2],seg2[5],seg2[6],seg2[7],seg2[8],seg2[3],seg2[4]}})
		-- end

		if #intersections > 0 then
			if intonly then
				for i, int in ipairs(intersections) do
					inters = inters + 1
					local x, y, t1, t2 = int[1], int[2], int[3], int[4]
					local s = 0.3
					local ss = seg1[10]
					segCuts[ss] = segCuts[ss] or {}
					table.insert(segCuts[ss], { x, y, t1, seg1[11] })
					ss = seg2[10]
					segCuts[ss] = segCuts[ss] or {}
					table.insert(segCuts[ss], { x, y, t2, seg2[11] })
				end
			else
				for i, int in ipairs(intersections) do
					inters = inters + 1
					local x, y, t1, t2 = int[1], int[2], int[3], int[4]
					local s = 0.3
					-- if showIntersection then
					-- editor_addTestLines({{x-s,y,x,y-s,x+s,y,x,y+s}})
					-- end
					segCuts[s1] = segCuts[s1] or {}
					table.insert(segCuts[s1], { x, y, t1 })
					segCuts[s2] = segCuts[s2] or {}
					table.insert(segCuts[s2], { x, y, t2 })
				end
			end
		end
	end

	-- print("intersections after first pass",ints)
	_time("time4")

	if intonly then
		for c, cuts in pairs(segCuts) do
			table.sort(cuts, intSort)
		end
		print("erase ready")
		return segCuts
	end

	for s = 1, segs[0][0], 1 do
		if segCuts[s] then
			table.sort(segCuts[s], segSort)
			local t = 0
			local x, y = segs[s][1], segs[s][2]
			for c = 1, #segCuts[s], 1 do
				local cut = segCuts[s][c]
				local newSeg = getNextSeg()
				fill_sliceCurve(segs[s], newSeg, t, cut[3])
				newSeg[1], newSeg[2] = x, y
				-- editor_addTestLines({{x,y,cut[1],cut[2],x,y}})
				x, y = cut[1], cut[2]
				newSeg[3], newSeg[4] = x, y
				t = cut[3]
				if trim then
					updateSegEnds(ends[newSeg[10]], newSeg)
				end
				updateSegBounds(newSeg)
				-- editor_addTestLines({{newSeg[1],newSeg[2],newSeg[3],newSeg[4]}})
			end
			local seg = segs[s]
			fill_sliceCurve(segs[s], tmpCurve, t, 1)
			tmpCurve[3], tmpCurve[4] = segs[s][3], segs[s][4]
			segs[s] = tmpCurve
			segs[s][1], segs[s][2] = x, y

			if trim then
				updateSegEnds(ends[seg[10]], segs[s])
			end
			updateSegBounds(seg)
			-- editor_addTestLines({{segs[s][1],segs[s][2],segs[s][3],segs[s][4]}})
		end
	end

	_time("time5")

	if not simple then
		checks = getSegChecks(segs)
		for c = 1, #checks - 1, 2 do
			local seg1 = segs[checks[c]]
			local seg2 = segs[checks[c + 1]]
			segTests = segTests + 1
			-- if intersect, split curve
			local x, y = intersect(seg1[1], seg1[2], seg1[3], seg1[4], seg2[1], seg2[2], seg2[3], seg2[4])
			if x then
				-- segmentN = segmentN + 1
				-- local new = segs[segmentN]
				-- splitCurve(seg1,seg1,new)
				-- sliceCurve(seg1,new,0.5,1)
				-- sliceCurve(seg1,seg1,0,0.5)
				-- editor_addTestLines({{seg1[1],seg1[2],seg1[3],seg1[4],seg1[1],seg1[2]}})
				-- editor_addTestLines({{new[1],new[2],new[3],new[4],new[1],new[2]}})

				local new = getNextSeg()
				-- splitCurve(seg2,seg2,new)
				fill_sliceCurve(seg2, new, 0.5, 1)
				fill_sliceCurve(seg2, seg2, 0, 0.5)
				-- editor_addTestLines({{seg2[1],seg2[2],seg2[3],seg2[4],seg2[1],seg2[2]}})
				-- editor_addTestLines({{new[1],new[2],new[3],new[4],new[1],new[2]}})
			end
		end
	end

	if animatePanel.debugView then
		-- for s=1,segs[0][0],1 do
		-- 	editor_addTestLines({{segs[s][1],segs[s][2],segs[s][3],segs[s][4]}})
		-- end
	end

	-- cut ends
	-- for s=1,#strokes,1 do
	-- 	if ends[s][0]>2 then
	-- 		local x1,y1,s1 = ends[s][5],ends[s][6],1
	-- 		local x2,y2,s2 = ends[s][7],ends[s][8],0.5
	-- 		editor_addTestLines({{x1-s1,y1,x1,y1-s1,x1+s1,y1,x1,y1+s1},{x2-s2,y2,x2,y2-s2,x2+s2,y2,x2,y2+s2}})
	-- 	end
	-- end

	_time("time6")

	local cuts = 0
	if trim then
		for s = segs[0][0], 1, -1 do
			local seg = segs[s]
			if seg[20] == 0 then -- don't trim fills
				local ende = ends[seg[10]]
				local cut
				if
					(ende[0] < 3 or seg[11] < ende[1] or seg[11] > ende[2])
					or (seg[11] == ende[1] and seg[12] < ende[3])
					or (seg[11] == ende[2] and seg[13] > ende[4])
				then
					cut = true
				end

				if cut then
					cuts = cuts + 1
					-- editor_addTestLines({{seg[1],seg[2],seg[3],seg[4]}})
					segs[s] = segs[segs[0][0]]
					segs[0][0] = segs[0][0] - 1
				end
			end
		end
	end

	-- local mdist = math.dist
	-- if simple then
	-- 	for s1=1,segs[0][0],1 do
	-- 		local seg1 = segs[s1]
	-- 		for s2=1,segs[0][0],1 do
	-- 			if s1~=s2 then
	-- 				local seg2 = segs[s2]
	-- 				local edit = seg1[18]>seg2[18] and seg1 or seg2
	-- 				local other = edit == seg1 and seg2 or seg1
	-- 				local a,b = edit[1]==other[1] and edit[2]==other[2],edit[1]==other[3] and edit[2]==other[4]
	-- 				local c,d = edit[3]==other[1] and edit[4]==other[2],edit[3]==other[3] and edit[4]==other[4]
	-- 				if a or b then
	-- 					local x,y = other[a and 3 or 1], other[a and 4 or 2]
	-- 					if math.abs(mdist(edit[1],edit[2], edit[3],edit[4])-(mdist(edit[1],edit[2],x,y)+mdist(x,y,edit[3],edit[4])))<0.000001 then
	-- 					-- print("editAB??",edit[1],edit[2],x,y)
	-- 						edit[1],edit[2] = x,y
	-- 					end
	-- 				elseif c or d then
	-- 					local x,y = other[c and 3 or 1], other[c and 4 or 2]
	-- 					if math.abs(mdist(edit[1],edit[2], edit[3],edit[4])-(mdist(edit[1],edit[2],x,y)+mdist(x,y,edit[3],edit[4])))<0.000001 then
	-- 					-- print("editCD??",edit[3],edit[4],x,y)
	-- 						edit[3],edit[4] = x,y
	-- 					end
	-- 				end
	-- 			end
	-- 		end
	-- 	end
	-- end

	-- print("intersection checks performed",segTests)
	-- print("intersections after second pass",inters)

	_time("time7")
  
	print("segments in:", startSegCount, "segments out:", segs[0][0])

	_end_timer("trim time")
	collectgarbage("restart")

	return segs, bbox, allInts, startSegCount
end

function fill_pointInFill(panel, shape, point, circle, force, detail)
	local detail = detail or 0
	local ln = shape.line
	local cline = detail == 0 and ln or cachedLine(panel, shape, force, detail)
	local x, y = point[3] and (point[1] + point[3]) / 2 or point[1], point[3] and (point[2] + point[4]) / 2 or point[2]

	-- print("check in fill")

	local inFill = false

	-- print("point within shape...")
	for l = 1, #cline, 1 do
		local line = cline[l]
		local time = engine_getTime()
		local inBox
		if ln[l][0][2] > 0 then
			inBox = x >= ln[l][0][3] - 1 and x <= ln[l][0][5] + 1 and y >= ln[l][0][4] - 1 and y <= ln[l][0][6] + 1
		else
			inBox = true
		end
		if l == 1 then
			local inShape
			if inBox then
				inShape = PointWithinShape(line, x, y, detail == 0)
			end
			-- POINT_IN_OUTER_TIME=POINT_IN_OUTER_TIME+(engine_getTime()-time)
			if inShape then
				inFill = true
			else
				return false, 0
			end
		else
			local inShape
			if inBox then
				inShape = PointWithinShape(line, x, y, detail == 0)
			end
			-- POINT_IN_HOLES_TIME=POINT_IN_HOLES_TIME+(engine_getTime()-time)
			if inShape then
				return false, l
			end
		end

		-- if l==1 then
		-- 	POINT_IN_OUTER_TIME=POINT_IN_OUTER_TIME+(engine_getTime()-time)
		-- else
		-- 	POINT_IN_HOLES_TIME=POINT_IN_HOLES_TIME+(engine_getTime()-time)
		-- end

		-- if circle and point[5]>0.1 then
		-- 	for i=1,line[0][0]-2,2 do
		-- 		if distPointToLineSegment2(x,y, line[i][1], line[i][2],line[i+1][1], line[i+1][2]) < point[5] then
		-- 			return true
		-- 		end
		-- 	end
		-- end
	end

	return inFill
end

function fill_prepareSegments(panel)
	if panel.eraseModesVector ~= "segment" then
		return
	end

	panel.register()
	local strokes = {}
	editor_processStrokes(panel, function(stroke)
		table.insert(strokes, stroke)
	end, panel.selectLayer)

	panel.eraserSegs = fill_splitSegments(strokes, false, false, true)
end

function fill_prepareFill(panel)
	local segments, bbox
	panel.recalcFills = false
	panel.register()
	local strokes = {}
	local fills = {}
	editor_processStrokes(panel, function(stroke)
		if stroke.group == "fills" then
			table.insert(fills, stroke)
		end
		table.insert(strokes, stroke)
	end, panel.fillLayer)
	fills = #fills > 0 and fills or false
	editor_clearFrameDebugStrokes(panel)
	panel.layersFilled = lume.clone(panel.layersToFill)
	segments, bbox = fill_splitSegments(strokes, panel.fillGap < 1, false, false, false, true)
	if not segments then
		return
	end

	panel.proccedStrokes = { segments, bbox }

	-- new fill coroutine
	if not panel.forceRefill then
		panel.lastFrame = panel.f.id

		panel.skipFillShapes = false --(panel.fillRadius==0 and not panel.fillAll)

		panel.lastFill = flood_fillVector(panel, segments, bbox, false, panel.skipFillShapes, fills) or panel.lastFill

		fill_addTestLines(panel.lastFill.testpolys)
		panel.filledFrame = false
	end

	return panel.proccedStrokes
end

function fill_addTestLines(testpolys)
	if not testpolys or (#testpolys == 0) or not animatePanel.debugView then
		return
	end
	local o = 0
	editor_refreshStrokes()
	for p, poly in ipairs(testpolys) do
		if #poly > 1 then
			local r, g, b = hsvToRgb(math.random(), 0.3 + math.random() * 0.3, 0.8)
			local randomColor = { r = r, g = g, b = b, a = 1 }
      local color = poly.color or randomColor
			local ox, oy = (poly.ox or 0) + (-o + math.random() * o), (poly.oy or 0) + (-o + math.random() * o)
			-- local poly2 = {}
			if type(poly[1]) == "table" then
        poly = poly[1]
      end
			-- 	for p = 1, #poly, 1 do
			-- 		if poly[p][3] and poly[p][3] ~= 0 then
			-- 			table.insert(poly2, { poly[p][3], poly[p][4] })
			-- 		end
			-- 		table.insert(poly2, { poly[p][1], poly[p][2] })
			-- 		if poly[p][5] and poly[p][5] ~= 0 then
			-- 			table.insert(poly2, { poly[p][5], poly[p][6] })
			-- 		end
			-- 	end
			-- else
			-- 	for p = 1, #poly - 1, 2 do
			-- 		table.insert(poly2, { poly[p], poly[p + 1] })
			-- 	end
			-- end
			-- poly = poly2
			-- table.insert(poly,poly[1])
			-- local vx, vy = 0, 0
			-- for p = 1, #poly, 1 do
			-- 	if (animatePanel.debugSmooth or poly.smooth) and p > 1 and p < #poly then
			-- 		poly[p][1], poly[p][2] =
			-- 			(poly[p][1] + poly[p - 1][1] + poly[p + 1][1]) / 3,
			-- 			(poly[p][2] + poly[p - 1][2] + poly[p + 1][2]) / 3
			-- 	end
			-- 	poly[p][1], poly[p][2] = poly[p][1] + ox, poly[p][2] + oy
			-- end

			-- print("addtestline",inspect(poly))
      table.insert(animatePanel.debugStrokes,{line=poly,color=color})
		end
	end
end

function fill_copyFrameCanvasToTemp(panel)
	if not editor_sameSizeTexture(panel.tempCanvas, panel.f.canvas) then
		panel.tempCanvas = newCanvas(panel.f.w, panel.f.h, panel.l.format)
	end

	gfx.push("all")
	gfx.setCanvas(panel.tempCanvas)
	gfx.clear(0, 0, 0, 0)
	gfx.setBlendMode("replace")
	gfx.setShader()
	gfx.setColor(1, 1, 1, 1)
	gfx.draw(panel.f.canvas)
	gfx.pop()
end

function fill_fillTool(panel, moving)
	mouse_pushUp(function()
		panel.clickedCanvas = false
	end)
	if moving and (not panel.fillDrag or not mouse.moved) then
		return
	end
	if panel.pickingColor then
		return
	end

	editor_pushColor(panel)

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

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

	local startPoint
	local startFill

	local segments, bbox

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

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

	local filled

	if startFill then
		if panel.l.format == "vector" then
			collectgarbage("stop")
			print("\nSTART FILL VECTOR --------")
			_start_timer("fill time")
			_time()
			panel.lastFill = panel.lastFill or {}
			local skipFill
			local r = panel.fillRadius
			local startBox = { startPoint.x - r, startPoint.y - r, startPoint.x + r, startPoint.y + r, r / 2 }

			for s, stroke in lume.ripairs(panel.f.strokes) do
				if stroke.debug then
					-- table.remove(panel.f.strokes,s)
				end
			end

			local fills = 0
			for s, stroke in ipairs(panel.f.strokes) do
				if stroke.group == "fill" then
					fills = fills + 1
				end
			end

			print("fills", fills)
			-- RECOLOR EXISTING FILL SHAPES

			local checked = 0
			print("#panel.f.strokes", #panel.f.strokes)
			for s = 1, #panel.f.strokes, 1 do
				checked = checked + 1
				local index = s == 1 and panel.lastFilledShape or (s == panel.lastFilledShape and 1 or s)
				local stroke = panel.f.strokes[index]
				stroke.index = index
				if stroke.group == "fill" then
					print("fill index", index)
					if not stroke.bbox then
						editor_strokeBounds(stroke, 1, true)
					end
					local inbox = bboxCollide1(startBox, stroke.bbox)
					if inbox then
						local inFill, hole = fill_pointInFill(panel, stroke, startBox, true)
						if inFill then
							-- local randomColor = {r=math.random()/4+0.6,g=math.random()/3+0.4,b=math.random()/3+0.4,a=1}
							local r1, g1, b1, a1 = unpackColor(randomColor or panel.fillColor)
							local r2, g2, b2, a2 = unpackColor(stroke.color)
							if not (r1 == r2 and g1 == g2 and b1 == b2 and a1 == a2) then
								stroke.color = lume.clone(randomColor or panel.fillColor)
								panel.filledSomething = true
								editor_refreshStrokes()
							end
							if startBox[5] == 0 then
								skipFill = true
								panel.lastFilledShape = index
								print("recolored fill")
								break
							end
						elseif hole > 0 then
							if sameColor(panel.fillColor, stroke.color) then
								table.remove(stroke.line, hole)
								stroke.tris = nil
								editor_refreshStrokes()
							else
								local new = newStroke({
									color = lume.clone(panel.fillColor),
									group = "fill",
									line = { copyLine(stroke.line[hole]) },
									point = { startPoint.x, startPoint.y },
								})
							end
						end
					end
				end
			end
			print("checked ", checked)
			print("panel.lastFilledShape", panel.lastFilledShape, #panel.f.strokes)

			if not skipFill then
				-- REMOVE DEBUG STROKES
				if panel.debugView or panel.fillAll then
					for s, stroke in lume.ripairs(panel.f.strokes) do
						if stroke.group == "fill" then
							table.remove(panel.f.strokes, s)
						end
					end
				end

				local startTime = engine_getTime()

				if not moving then
					local layersChanged = #panel.layersToFill ~= #panel.layersFilled
					if not layersChanged then
						for l = 1, #panel.layersFilled, 1 do
							-- print(panel.layersToFill[l],panel.layersFilled[l],panel.layersToFill[l]~=panel.layersFilled[l])
							if panel.layersToFill[l] ~= panel.layersFilled[l] then
								layersChanged = true
							end
						end
					end

					print("layersChanged", layersChanged)
					local gapChanged = panel.prevFillGap ~= panel.fillGap
					if gapChanged or layersChanged or panel.recalcFills then
						print("reprep fill")
						fill_prepareFill(panel)
						segments, bbox = panel.proccedStrokes[1], panel.proccedStrokes[2]
					end

					if panel.forceRefill then
						print("refill fill")
						panel.lastFill = flood_fillVector(panel, segments, bbox, true, panel.skipFillShapes)
							or panel.lastFill
							or {}
					else
						if panel.fillThread and coroutine.status(panel.fillThread) ~= "dead" then
							panel.finishFill = true
							print("FINISH FILL")
							coroutine.resume(panel.fillThread)
							print("FILL FINISHED")
						end
					end
				end

				if panel.lastFill then
					print("panel.skipFillShapes", panel.skipFillShapes)
					if panel.skipFillShapes then
						print("recalc fill")
						local startPoint = vec2(panel.brushCoords.ax, panel.brushCoords.ay)
						local shapes = flood_groupTriangles(panel, startPoint)
						concat(panel.lastFill.shapes, shapes)
					elseif panel.fillAll then
						flood_groupTriangles(panel)
					end
				end

				panel.finishFill = false

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

				-- REMOVE POSSIBLE FILL REGION IF AN EXISTING FILL EXISTS IN THE FRAME THAT MATCHS(~) IT

				function sampleFills(panel, shape)
					local cline = cachedLine(panel, shape)
					local line = cline[1]
					local px, py = shape.point[1], shape.point[2]
					for st, stroke in ipairs(panel.f.strokes) do
						if stroke.group == "fill" and pointInBBox(stroke.bbox, px, py) then
							stroke.index = st
							if fill_pointInFill(panel, stroke, shape.point) then
								if startBox[5] > 0 then
									return 1
								else
									if panel.fillAll then
										return 1
									else
										return 2
									end
								end
							end
						end
					end
					return 0
				end

				if shapes and shapes[1] then
					function fillFills(pass2)
						for s, shape in ipairs(shapes) do
							shape.index = s
							if
								not panel.fillsUsed[s]
								and (
									panel.fillAll
									or (
										bboxCollide1(startBox, shape.bbox)
										and fill_pointInFill(panel, shape, startBox, true)
									)
								)
							then
								-- print("fill shape ",s)
								local cline = cachedLine(panel, shape)
								local sample
								sample = sampleFills(panel, shape)

								if sample == 1 then
									goto continue
								end
								if sample == 2 then
									goto done
								end

								panel.fillsUsed[s] = true
								local randomColor
								-- if config.debugRandomColor then
								-- randomColor = {r=math.random()/2+0.6,g=math.random()/2+0.4,b=math.random()/2+0.4,a=0.9}
								-- end
								local new = newStroke({
									bbox = shape.bbox,
									color = randomColor or lume.clone(panel.fillColor),
									group = "fill",
									line = shape.line,
									point = shape.point,
								})
								table.insert(panel.f.strokes, new)
								table.insert(
									panel.strokeDiffs.added,
									{ frame = panel.f, stroke = panel.f.strokes[#panel.f.strokes] }
								)
								panel.filledSomething = true
								editor_refreshStrokes()
								if not (panel.fillAll or startBox[5] > 0) then
									break
								end
							end
							::continue::
						end
						::done::
						if not pass2 then
							fillFills(true)
						end
					end
					fillFills()
				end

				-- SHOW TRIANGULATION
				if panel.debugView then
					fill_addTestLines(testpolys)
					editor_refreshStrokes()
				end

				-- SHOW CONSTRAINED REGIONS
			end
			_end_timer("fill time")
			local fills = 0
			for s, stroke in ipairs(panel.f.strokes) do
				if stroke.group == "fill" then
					fills = fills + 1
				end
			end

			print("fills", fills)
			print("END FILL VECTOR ------\n")
		else
			local flooded = runFloodFill(startPoint.x, startPoint.y, panel.fillStack)
			if flooded then
				panel.f.dirty = true
				-- panel.filledData:encode("png","filldata.png")
				filled = gfx.dataToCanvas(panel.filledData)
				print("prefilled")
				-- editor_paletteCheck(panel.fillColor)
				-- editor_drawBrushTexture (panel, filled)

				fill_copyFrameCanvasToTemp(panel)

				gfx.push("all")

				local r, g, b, a = unpackColor(panel.fillColor)
				gfx.setColor(r, g, b, a ^ 0.5)

				if panel.fillBehind then
					gfx.push("all")
					gfx.setCanvas(panel.f.canvas)
					gfx.clear(0, 0, 0, 0)
					gfx.pop()

					gfx.setBlendMode("replace")
					gfx.setColor(r, g, b, a)
				end

				gfx.setCanvas(panel.f.canvas)
				gfx.setShader()
				if panel.fillExpand > 0 then
					gfx.setShader(shaders.expandShader)
					shaders.expandShader:send("Size", panel.fillExpand)
					shaders.expandShader:send("Resolution", { filled:getWidth(), filled:getHeight() })
				end
				gfx.setFilter(filled, panel.filter)
				gfx.draw(filled)
				gfx.setShader()

				if panel.fillBehind then
					filled, panel.f.canvas = panel.f.canvas, filled
					gfx.push("all")

					gfx.setCanvas(panel.f.canvas)
					gfx.setBlendMode("replace")
					gfx.setShader(shaders.alphaMultiply)
					shaders.alphaMultiply:send("dest", filled)
					-- shaders.alphaMultiply:send("coords",{})
					gfx.setColor(1, 1, 1, 1)
					gfx.draw(panel.tempCanvas)

					gfx.pop()
				end

				gfx.pop()
				panel.pushUndoData({ rasterDiff = editor_packCanvas(panel.f.canvas, panel.tempCanvas) })
			else
				editor_tempTip("Bad Region!")
			end
		end
	else
		if panel.format ~= "vector" then
			editor_tempTip("Out of bounds")
		end
	end
end

function fill_resetFill(panel, ref, point)
	panel.fillTestData = ref:newImageData()
	panel.filledData = gfx.newImageData(panel.fillDims.w, panel.fillDims.h)
	local x, y = point.x, point.y
	print("filling from: " .. x .. " " .. y)
	if x < 1 or y < 1 or x > panel.fillTestData:getWidth() - 1 or y > panel.fillTestData:getHeight() - 1 then
		return false
	end
	local r, g, b, a = panel.fillTestData:getPixel(x, y)
	panel.fillStart = { r = r, g = g, b = b, a = a }
	panel.fillStack = {}

	panel.fillGrid = ffi.new(string.format("bool[%i][%i]", panel.fillDims.h, panel.fillDims.w))
	panel.fillRef = ffi.new(string.format("bool[%i][%i]", panel.fillDims.h, panel.fillDims.w))
	panel.fillDataSize = panel.fillDims.h * panel.fillDims.w
	return true
end
