local dist = math.dist
local sin = math.sin
local cos = math.cos
local tan = math.tan
local sqrt = math.sqrt
local min, max = math.min, math.max
local empty = {}
local slice = {}
local tempbuffer = {}
local tempbuffer2 = {}
local tempbuffer3 = {}
local midVec = vec2()
local tmpBez = mth.newBezierCurve(0, 0, 0, 0, 0, 0, 0, 0)
local vBufferN = 0
local vBufferSize = 500000
local vBuffer = ffi.cast("strkvec*", ffi.C.malloc(ffi.sizeof("strkvec") * vBufferSize))
local max_points = 500000
local triangle_data = ffi.new("int[?]", max_points)
local segment_data = ffi.new("int[?]", max_points)
local vertex_data = ffi.new("float[?]", max_points)
local point_data = ffi.new("float[?]", max_points)
local holes = ffi.new("float[?]", max_points)
local out_data = ffi.new("float[?]", max_points)
local strokeColor = ffi.new("float[6]")
local tempSegs = ffi.new(VECTOR_TYPE .. "[1][24]")
local tempVerts = ffi.new("unsigned int[100000][2]")
local bezSize = 200000
local bezBuffer = bezBuffer or ffi.new("float[?][6]", bezSize)
local zvalues = { rough = 0, line = 0.5, note = 0.9 }
local stroke_props = ffi.new("stroke_prop_t")
local ldbf = ffi.new("float[?]", 2 ^ 17)
bezBuffer[0][1] = bezSize

local function getFillCat(panel)
	return 0 --mc(2,(config.catmull or 0)+(panel.cat or 0)-1,3)
end

local function vBufInsert(x, y, z, w, u, v, r, g, b, a, nx, ny, nz)
	-- if vBufferN>vBufferSize*0.9 then
	--   print("OUT OF BOUNDS",vBufferN,vBufferSize)
	--   vBufferSize = vBufferSize*1.4
	--    local newBuffer = ffi.new("strkvec[?]",vBufferSize)
	--    ffi_copy(newBuffer,vBuffer,ffi.sizeof("strkvec")*vBufferN)
	--    vBuffer = newBuffer
	--    vBuffer = vBuffer
	--    print("EXPANDED STRK BUFF",vBufferN,vBufferSize)
	-- end
	vBufferN = vBufferN + 1
	vBuffer[vBufferN].x = x
	vBuffer[vBufferN].y = y
	vBuffer[vBufferN].z = z + vBufferN / 1000000
	-- vBuffer[vBufferN].w  = w;
	vBuffer[vBufferN].u = u
	vBuffer[vBufferN].v = v
	vBuffer[vBufferN].r = r
	vBuffer[vBufferN].g = g
	vBuffer[vBufferN].b = b
	vBuffer[vBufferN].a = a
	vBuffer[vBufferN].nx = nx or 0
	vBuffer[vBufferN].ny = ny or 0
	-- vBuffer[vBufferN].nz  = nz or 0;
end

local function startVerts()
	vBufferN = 0
end

local function groupSort(a, b)
	return a.group < b.group
end

local function segSort(a, b)
	return a[3] < b[3]
end

local function windingSide(ax, ay, bx, by, x, y)
	return (bx - ax) * (y - ay) - (x - ax) * (y - ay)
end

local function splitPolyLine(line, retry)
	local splits = { { line[1], line[2] } }
	local current = 1
	local splitsFinished = {}
	local splitStart = 3
	local px, py
	-- if retry then
	--   table.insert(line,lerp(line[#line-1],line[1],0.99))
	--   table.insert(line,lerp(line[#line],  line[2],0.99))
	-- end
	local tests = 0
	for l1 = 1, #line - 2, 2 do
		local inters = {}
		for l2 = 1, #line - 2, 2 do
			if l2 < l1 - 1 or l2 > l1 + 1 then
				local x, y
				if line[l1] == line[l2] and line[l1 + 1] == line[l2 + 1] then
					x, y = line[l1], line[l1 + 1]
				else
					x, y = intersect(
						line[l1],
						line[l1 + 1],
						line[l1 + 2],
						line[l1 + 3],
						line[l2],
						line[l2 + 1],
						line[l2 + 2],
						line[l2 + 3]
					)
				end
				if x then
					tests = tests + 1
					if retry then
						print("CROSSSSSSSSSSSSSSSSSS")
					end
					table.insert(inters, { x, y, dist(line[l1], line[l1 + 1], x, y) })
				end
			end
		end

		if #inters > 0 then
			table.sort(inters, segSort)

			for i = 1, #inters, 1 do
				local x, y = inters[i][1], inters[i][2]
				local continue
				local finish

				for s = 1, #splits, 1 do
					if not splitsFinished[s] then
						local split = splits[s]
						if split[1] == x and split[2] == y then --end split
							splitsFinished[s] = true
							finish = true
						end
						if s ~= current and split[#split - 1] == x and split[#split] == y then -- continue
							continue = s
						end
					end
				end

				concat(splits[current], splice(line, splitStart, l1 + 1))
				if not finish then
					table.insert(splits[current], x)
					table.insert(splits[current], y)
				end

				if continue then
					current = continue
				else
					table.insert(splits, { x, y })
					current = #splits
				end
				px, py = x, y
			end
			splitStart = l1 + 2
		end
	end
	-- table.remove(line,#line)
	-- table.remove(line,#line)
	concat(splits[current], splice(line, splitStart, #line))
	if retry and tests > 0 then
		print("intersections", tests)
	end
	return splits
end

local function polyArea(poly)
	local N = #poly + 1
	table.insert(poly, poly[1])
	table.insert(poly, poly[2])
	table.insert(poly, poly[3])
	table.insert(poly, poly[4])

	local area = 0

	for i = 2, #poly - 3, 2 do
		area = area + poly[i] * (poly[i + 3] - poly[i - 1])
	end
	area = area / 2

	return area
end

local function splitSort(a, b)
	return #a > #b
end

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

local function invert(line)
	local line2 = {}
	for l = line[0][0] - 1, 1, -2 do
		table.insert(line2, line[l])
		table.insert(line2, line[l + 1])
	end
	return line2
end

local function pointSort(a, b)
	if a[1] == b[1] then
		return a[2] < b[2]
	else
		return a[1] < b[1]
	end
end

local function renderStroke(stroke, layer, frame, cat, segs, preview)
	if stroke.line[0][0] < 2 then
		return
	end
	bench.push("prepstroke")

	local r, g, b, a = unpackColor(
		stroke.debug and stroke.color
			or (
				stroke.group == "rough" and animatePanel.penGroups[1].color
				or (stroke.group == "note" and animatePanel.penGroups[3].color or stroke.color)
			)
	)

	local a = (animatePanel.debugView and animatePanel.debugThin and stroke.group == "line" and not stroke.debug)
			and 0.1
		or a
		or 1
	local w = (animatePanel.debugView and animatePanel.debugThin and stroke.group == "line" and not stroke.debug)
			and 0.1
		or stroke.size / 2
	local z = 0 + (zvalues)[stroke.group]

	strokeColor[0], strokeColor[1], strokeColor[2], strokeColor[3], strokeColor[4], strokeColor[5] =
		r, g, b, preview and 1 or a, w, z

	local layer = layer or frame.layer

	bench.pop("prepstroke")

	-- _LOG("bezier stroke...")
	local buffer = vector_bezierLine(stroke, stroke.line, stroke.debug and 0 or math.max(0, (cat - 1)), false)
	local line, length = buffer, buffer[0][0]

	local ends = stroke.ends == "tapered" and 2 or (stroke.ends == "round" and 1 or 0)
	local size = stroke.size

	local wobble = layer.shouldWobble and layer.wobble or 0
	local onePixel = layer.onePixel
	local lineWidth = stroke.debug and 2 or layer.lineWidth
	local lineWidthOverride = layer.lineWidthOverride

	if length < 2 then
		return
	end

	bench.push("genstroke")
	-- _LOG("genstroke...")

	-- print(line[1][1],line[1][2],line[2][1],line[2][2])

	stroke_props.vBufferN = vBufferN
	stroke_props.vBufferSize = vBufferSize
	stroke_props.length = length
	stroke_props.ends = ends

	stroke_props.onePixel = onePixel == true
	stroke_props.lineWidthOverride = lineWidthOverride == true
	stroke_props.writeSegs = segs and true or false
	stroke_props.preview = preview == true

	stroke_props.size = size
	stroke_props.wobble = wobble
	stroke_props.lineWidth = lineWidth

	vBufferN = ffi.C.genstroke(vBuffer, tempVerts, line, segs or tempSegs, strokeColor, stroke_props)
	-- _LOG("  success!")
	bench.pop("genstroke")
	return length
end

local function triangulateFill(stroke, cat)
	local printfill = true -- config.debugTriangulation and true or false
	_start_timer("Triangulate")
	_time()

	local numpoints, numsegs, numholes = 0, 0, 0
	local counts = ffi.new("int[10]")
	counts[2] = 1

	local minHoleLength = 3
	local minHullLength = 3

	local point = { 0, 0 }

	-- cachedLine(animatePanel,stroke,true,0)

	-- print("#stroke.line",#stroke.line)
	local points_est = stroke.line[1][0][0]
	local cline = stroke.line --cachedLine(animatePanel,stroke,false,cat)
	if #stroke.line > 1 then
		for l = 2, #stroke.line, 1 do
			points_est = points_est + stroke.line[l][0][0]
			if stroke.line[l][0][0] > minHoleLength then
				local ax, ay, bx, by = cline[l][1][1], cline[l][1][2], cline[l][2][1], cline[l][2][2]
				local d = math.dist(ax, ay, bx, by) * 10000
				local mx, my = (ax + bx) / 2, (ay + by) / 2
				local ux, uy = -(by - ay) / d, (bx - ax) / d
				local s = PointWithinShape(cline[l], mx + ux, my + uy, true) and 1 or -1
				holes[numholes * 2], holes[numholes * 2 + 1] = mx + s * ux, my + s * uy
				stroke.line[l][0][7], stroke.line[l][0][8] = mx + s * ux, my + s * uy
				numholes = numholes + 1
			end
		end
	end
	_time("find holes")

	-- create grid for points to speed up prefiltering
	local bbox = stroke.bbox or editor_strokeBounds(stroke)
	local w, h = bbox[3] - bbox[1], bbox[4] - bbox[2]
	local gridw, gridh = 8, 8
	local tw, th = w / gridw, h / gridh
	local pgrid = ffi.new(string.format("u32[%i][%i][%i]", gridw + 1, gridh + 1, points_est / 2))
	_time("bounds/alloc")
	--filter points
	for l = 1, #stroke.line, 1 do
		local line = cline[l]
		if line[0][0] > minHullLength then
			local lastPointIndex
			local firstPointIndex

			for i = 1, line[0][0], 1 do
				local found = false
				local x, y =
					math.min(math.max(0, math.floor((line[i][1] - bbox[1]) / tw)), gridw - 1),
					math.min(math.max(0, math.floor((line[i][2] - bbox[2]) / th)), gridh - 1)

				if pgrid[y][x][0] > 0 then
					for j = 1, pgrid[y][x][0], 1 do
						local p = pgrid[y][x][j]
						if line[i][1] == point_data[p * 2] and line[i][2] == point_data[p * 2 + 1] then
							if lastPointIndex then
								-- if printfill then print("FOUND") end
								segment_data[numsegs * 2] = lastPointIndex
								segment_data[numsegs * 2 + 1] = p
								numsegs = numsegs + 1
							end
							lastPointIndex = p
							found = true
							break
						end
					end
				end

				if not found then
					if not firstPointIndex then
						firstPointIndex = numpoints
					end
					point_data[numpoints * 2], point_data[numpoints * 2 + 1] = line[i][1], line[i][2]

					pgrid[y][x][0] = pgrid[y][x][0] + 1
					pgrid[y][x][pgrid[y][x][0]] = numpoints

					if lastPointIndex then
						segment_data[numsegs * 2] = lastPointIndex
						segment_data[numsegs * 2 + 1] = numpoints
						numsegs = numsegs + 1
					end
					lastPointIndex = numpoints

					numpoints = numpoints + 1
				end
			end

			-- print("line[0][0]",line[0][0])
			if not (lastPointIndex and firstPointIndex) then
				print(string.format("point indexes at %i: %i %i", l, lastPointIndex or -1, firstPointIndex or -1))
				return
			end

			segment_data[numsegs * 2] = lastPointIndex
			segment_data[numsegs * 2 + 1] = firstPointIndex
			numsegs = numsegs + 1
		end
	end

	if numpoints == 0 or numsegs == 0 then
		print("no points for fill, return")
		return
	end

	-- print("numpoints,numsegs",numpoints,numsegs)

	_time("filter")

	-- check for self intersection
	-- for i=numsegs-1,0,-1 do
	--   local a,b = segment_data[i*2],segment_data[i*2+1]
	--   for j=numsegs-1,0,-1 do
	--     if i~=j then
	--       local c,d  = segment_data[j*2],segment_data[j*2+1]
	--       if  ((a==c and b==d) or (b==c and a==d)) then
	--         segment_data[i*2],segment_data[i*2+1] = segment_data[numsegs*2],segment_data[numsegs*2+1]
	--         numsegs = numsegs-1
	--         if printfill then
	--           print(" remove duplicate ----------------")
	--         end
	--       else
	--         -- local p1x,p1y,p2x,p2y = point_data[a*2],point_data[a*2+1],point_data[b*2],point_data[b*2+1]
	--         -- local p3x,p3y,p4x,p4y = point_data[c*2],point_data[c*2+1],point_data[d*2],point_data[d*2+1]
	--         -- -- print("l1" , p1x,p1y,p2x,p2y)
	--         -- -- print( " l2",p3x,p3y,p4x,p4y)
	--         -- local x,y = intersect(p1x,p1y,p2x,p2y, p3x,p3y,p4x,p4y)
	--         -- if x then
	--         --   print(p1x,p1y,p2x,p2y)
	--         --   print(p3x,p3y,p4x,p4y)
	--         --   print(x,y)
	--         --   print("THIS IS A PROBLEM ]]]]]]]]]]]]]]]")
	--         --   editor_tempTip("fill failed: self intersection")
	--         --   return
	--         -- end
	--       end
	--     end
	--   end
	-- end
	-- for i=numsegs-1,0,-1 do
	--   local a,b = segment_data[i*2],segment_data[i*2+1]
	--   local p1x,p1y,p2x,p2y = point_data[a*2],point_data[a*2+1],point_data[b*2],point_data[b*2+1]
	-- end
	-- if printfill then _time("intersect") end

	local numtris, numverts

	if numpoints < 3 then
		return
	end

	if config.triggerBreakpoint then
		counts[3] = 1
	end
	numtris = ffi.C.ctriangulate(
		point_data,
		segment_data,
		holes,
		triangle_data,
		out_data,
		counts,
		ffi.new("triangle_params", numpoints, numsegs, numholes, true)
	)

	numtris = numtris
	stroke.point = {
		(out_data[0] + out_data[2] + out_data[4]) / 3,
		(out_data[1] + out_data[3] + out_data[5]) / 3,
	}
	-- numtris,numverts = counts[0], counts[1]
	-- print("DONE!, triangles:", numtris)
	-- print("numhoooooooooooooooooooooles",numholes)

	-- for t=0,numtris-1,1 do
	--   local x1,y1,x2,y2,x3,y3 =
	--     out_data[t*6],out_data[t*6+1],
	--     out_data[t*6+2],out_data[t*6+3],
	--     out_data[t*6+4],out_data[t*6+5]
	--   if math.max(x1,x2,x3)>2000 or math.min(x1,x2,x3)<-2000
	--     or math.max(y1,y2,y3)>2000 or math.min(y1,y2,y3)<-2000 then
	--     print("out of bounds",x1,y1,x2,y2,x3,y3)
	--   end
	-- end

	_time("gen tris")
	_end_timer("Triangulate")
	if numtris == 0 then
		return
	end

	return out_data, numtris
end

function newStrokeLine(len)
	local len = len + 1

	-- GC VERSION
	-- local new = ffi.new(STROKE_TYPE,len)
	-- new[0][1]=len

	local tipe = string.format("float(&)[%i][%i]", len, 10)

	local new = ffi.cast(tipe, ffi.C.calloc(len, STROKEPT_SIZE))
	-- print(string.format("calloced line %0.3f kb (%i bytes) (%i*%i)",(len*STROKEPT_SIZE)/(1024),len*STROKEPT_SIZE,len,STROKEPT_SIZE))
	_CALLOCED = _CALLOCED + len * STROKEPT_SIZE

	new[0][0] = 0
	new[0][1] = len

	return new
end

function copyLine(line)
	local len = line[0][1]
	local copy = newStrokeLine(len)
	print("  bout to copy", copy[0][1], ffi.sizeof(copy), copy[0][1] * STROKEPT_SIZE)
	ffi_copy(copy, line, ffi.sizeof(line))
	print("  copied")
	return copy
end

function copyFill(fill)
	local copy = {}
	for l, line in ipairs(fill) do
		copy[l] = copyLine(line)
	end
	return copy
end

function cleanupStroke(stroke)
	if stroke.group == "fill" then
		-- print("    destroying fill")
		for l, line in ipairs(stroke.line) do
			ffi.C.free(line)
		end
	else
		print("    destroying stroke", stroke.line)
		ffi.C.free(stroke.line)
		stroke.line = nil
		print("  destroyed stroke", stroke.line)
	end
end

function cleanupStrokeLine(line)
	ffi.C.free(line)
end

function loadStrokeLine(str)
	local s = 1 -- use all those bitz
	local len = #str / 32 - 1
	local line = newStrokeLine(len)
	line[0][0] = len
	ffi_copy(ldbf, str)
	for l = 1, len, 1 do
		-- print(l,len)
		-- local s = 1/l
		line[l][1], line[l][2], line[l][3], line[l][4], line[l][5], line[l][6], line[l][7], line[l][8] =
			s * ldbf[l * 8],
			s * ldbf[l * 8 + 1],
			s * ldbf[l * 8 + 2],
			s * ldbf[l * 8 + 3],
			s * ldbf[l * 8 + 4],
			s * ldbf[l * 8 + 5],
			ldbf[l * 8 + 6] / 4000,
			ldbf[l * 8 + 7] / 4000
	end
	-- print("loaded")
	return line
end

function newStroke(stroke)
	local new = type(stroke) == "table" and stroke or {}
	-- new.size   = new.size or size
	-- new.ends   = new.ends or ends
	-- new.group = new.group or group
	-- new.color = new.color or color

	if new.line then
		if type(stroke.line) == "table" then
			if type(stroke.line[1]) == "table" then
				if type(stroke.line[1][1]) == "table" then
					for l, line in ipairs(stroke.line) do
						new.line[l] = newStrokeLineFromTable(line)
					end
				else
					new.line = newStrokeLineFromTable(stroke.line)
				end
			elseif type(stroke.line[1]) == "string" then
				for l, line in ipairs(stroke.line) do
					new.line[l] = loadStrokeLine(line)
				end
			else
				-- print("pass thru stroke")
				new.line = stroke.line
			end
		elseif type(stroke.line) == "string" then
			new.line = loadStrokeLine(stroke.line)
		end
	else
		new.line = newStrokeLine(type(stroke) == "number" and stroke or 1000)
	end

	if not new.bbox then
		new.bbox = editor_strokeBounds(stroke)
	end
	new.id = new.id or simpleuuid()

	setGC(new, cleanupStroke)
	return new
end

function copyStroke(stroke, a, b)
	-- print("copy",stroke.group,type(stroke.line))
	stroke.bbox = stroke.bbox or editor_strokeBounds(stroke)

	if type(stroke.line) ~= "table" then
		if stroke.line[0][0] == 0 then
			error("empty stroke")
		end
	end

	local copy = {
		color = stroke.color and lume.clone(stroke.color) or nil,
		size = stroke.size,
		group = stroke.group,
		ends = stroke.ends,
		bbox = ffi.new("float[5]", stroke.bbox),
		id = stroke.id,
	}

	if type(stroke.line) == "table" then
		-- print("copy fill?")
		copy.line = copyFill(stroke.line)
	else
		-- print("copy linee")
		if a then
			copy.id = simpleuuid()
		end
		copy.line = a and spliceStroke(stroke.line, a, b) or copyLine(stroke.line)
	end
	return newStroke(copy)
end

function copyStrokes(source)
	local strokes = {}
	if type(source) == "table" and #source > 0 then
		for s, stroke in ipairs(source) do
			if type(stroke) == "table" and stroke.line and not stroke.debug then
				table.insert(strokes, copyStroke(stroke))
			end
		end
	end
	return strokes
end

function copyStrokeDiffs(source)
	print("copy stroke diffs")
	if type(source) == "table" and #source > 0 then
		for s, strk in ipairs(source) do
			local stroke = strk.stroke
			if stroke and stroke.line and not stroke.debug then
				strk.stroke = copyStroke(stroke)
			end
		end
	end
	return source
end

function spliceStroke(t, first, last, t2)
	if first > t[0][0] then
		return
	end
	local last = last or t[0][0]
	local len = last - first
	if len < 0 then
		print("splicestroke", first, last, len)
		error()
	elseif len == 0 then
		return newStrokeLine(2)
	end
	local new = newStrokeLine(len + 1)
	local t2 = t2 or new
	for i = first or 1, last, 1 do
		t2[0][0] = t2[0][0] + 1
		t2[t2[0][0]] = t[i]
	end
	-- print("spliced")
	return t2
end

function shrinkStrokeLine(stroke)
	local line = stroke.line
	-- if line[0][0]+3>line[0][1] then
	--  local size = line[0][0]+3
	--  local new = newStrokeLine(size)
	--  ffi_copy(new,line,STROKEPT_SIZE*size)
	--  new[0][1]=size
	--  stroke.line = new
	--  line = stroke.line
	-- end
	return line
end

function growStrokeLine(stroke, l, add)
	local line = l and stroke.line[l] or stroke.line
	-- print(line[0][0],line[0][1])
	if line[0][0] > line[0][1] - (add or 3) then
		local size = math.ceil(line[0][0] * 1.2 + 2)
		local new = newStrokeLine(size)
		print("copy", STROKEPT_SIZE * (line[0][0] + 1))
		ffi_copy(new, line, STROKEPT_SIZE * (line[0][0] + 1))
		cleanupStrokeLine(line)
		print("size1", line[0][1], "size2", line[0][1])
		new[0][1] = size
		if l then
			stroke.line[l] = new
		else
			stroke.line = new
		end
		line = l and stroke.line[l] or stroke.line
		print("cleaned")
	end
	return line
end

function popStrokeLinePoint(stroke)
	local line = stroke.line
	local l = line[0][0]
	line[0][0] = l - 1
end

function appendStrokeLinePoint(stroke, x, y, ax, ay, bx, by, w, a)
	-- print("append")
	-- print_info(3)
	local line = growStrokeLine(stroke, nil, 20)

	line[0][0] = line[0][0] + 1
	local pt = line[line[0][0]]
	pt[1], pt[2], pt[3], pt[4], pt[5], pt[6], pt[7], pt[8] = x, y, ax, ay, bx, by, w, a

	-- print(line[0][0],line[0][1],line[0][0]>line[0][1]-1)
	-- print("read",line[0][0],line[0][1])
end

function insertStrokeLinePoint(stroke, l, index, x, y, ax, ay, bx, by, w, a)
	local line = growStrokeLine(stroke, l)

	for i = line[0][0], index + 1, -1 do
		line[i + 1] = line[i]
	end

	line[0][0] = line[0][0] + 1
	local pt = line[index]
	pt[1], pt[2], pt[3], pt[4], pt[5], pt[6], pt[7], pt[8] = x, y, ax, ay, bx, by, w, a
end

function prependStrokeLinePoint(stroke, x, y, ax, ay, bx, by, w, a)
	print("prepend", stroke.line[0][0], stroke.line[0][1])
	local line = growStrokeLine(stroke, nil, 20)
	print("postprepend", line[0][0], line[0][1])

	for i = line[0][0] + 1, 1, -1 do
		line[i + 1] = line[i]
	end

	print("read")
	line[0][0] = line[0][0] + 1
	print("read2")
	local pt = line[1]
	pt[1], pt[2], pt[3], pt[4], pt[5], pt[6], pt[7], pt[8] = x, y, ax, ay, bx, by, w, a
	print("read3")
end

function newStrokeLineFromTable(t)
	if t[1] and t[1][1] then
		local new = newStrokeLine(#t)
		new[0][0] = #t
		for i = 1, #t, 1 do
			new[i][1] = t[i][1]
			new[i][2] = t[i][2]
			new[i][7] = 1
			new[i][8] = 1
		end
		return new
	end
end

function newStrokeLineFromCdata(t)
	local new = newStrokeLine(t[0][0])
	new[0][0] = t[0][0]
	new[0][2] = t[0][2]
	new[0][3] = t[0][3]
	new[0][4] = t[0][4]
	new[0][5] = t[0][5]
	new[0][6] = t[0][6]
	for i = 1, t[0][0], 1 do
		new[i][1] = t[i][1]
		new[i][2] = t[i][2]
		new[i][3] = t[i][1]
		new[i][4] = t[i][2]
		new[i][5] = t[i][1]
		new[i][6] = t[i][2]
		new[i][7] = 1
		new[i][8] = 1
	end
	return new
end

function vector_bezierLine(stroke, line, detail, fill)
	local dist = math.dist
	local b = 0
	local x, y, sx, sy
	local testTris = 0
	local buffer
	local line = line

	if type(line) == "number" then
		line = stroke.line[line]
	end

	bench.push("startbez")
	bench.pop("startbez")
	bench.push("makebez")

	ffi.C.makebez(bezBuffer, line, detail)

	bench.pop("makebez")
	return bezBuffer
end

function vector_renderStrokeOut(stroke, layer, frame, cat, segs, preview)
	startVerts()
	renderStroke(stroke, layer, frame, cat, segs, preview)
	return vBuffer, vBufferN
end

function vector_genVectorSelectionMesh(panel)
	vector_genVectorFrameMesh(panel)
end

function vector_genVectorFrameMesh(panel, layer, frame, onion, drawing)
	-- for s,stroke in lume.ripairs(panel.f.strokes) do
	--   if stroke.debug then
	--     table.remove(panel.f.strokes,s)
	--   end
	-- end

	bench.push("genstroke")
	bench.pop("genstroke")
	bench.push("startbez")
	bench.pop("startbez")
	bench.push("makebez")
	bench.pop("makebez")
	bench.push("sortstrokes")
	bench.pop("sortstrokes")
	bench.push("prepstroke")
	bench.pop("prepstroke")

	local peel = onion or 0
	local layer, frame = layer, frame
	local strokes, groupCounts, restroke, selection
	local cat = (config.catmull or 0) + (panel.cat or 0)
	cat = config.catmull > 0 and math.max(1, cat) or 0

	if not layer then -- for selection outline
		strokes = panel.strokeSelected.dragging and { panel.strokeSelected.stroke } or panel.selection.strokes
		restroke = true
		selection = true
	else
		strokes = frame.strokes
		strokes = type(strokes) == "table" and strokes or {}
		if (strokes and #strokes > 0 and not layer.strokeMeshes) or panel.sudoForceRestroke then
			editor_refreshStrokes()
		end

		layer.shouldWobble = layer.wobble > 0 and panel.playing and panel.ticked2

		restroke = panel.forceRestroke or layer.shouldWobble

		if
			panel.liveTransformPreview
			and peel == 0
			and (panel.scalingSelection or panel.movingSelection or panel.rotatingSelection)
		then
			if not sudoForceRestroke then
				panel.sudoForceRestroke = true
			end
			cat = math.max(1, cat - 1)
		end

		layer.strokeMeshes = layer.strokeMeshes or {}
		layer.strokeFrames = layer.strokeFrames or {}

		if onion then
			restroke = true
		end
	end

	if not strokes then
		return
	end
	if not (restroke or drawing) then
		return
	end

	local vx, vy, len = 0, 0, 0

	local vertCounts = {
		fill = 0,
		line = 0,
		rough = 0,
		note = 0,
	}

	local black = { r = 0, g = 0, b = 0, a = 1 }

	if drawing then
		-- print("PREVIEW STROKE")

		local stroke = panel.previewStroke

		editor_prepareToolMouse(panel)
		appendStrokeLinePoint(
			stroke,
			panel.brushCoords.ax,
			panel.brushCoords.ay,
			panel.brushCoords.ax,
			panel.brushCoords.ay,
			panel.brushCoords.ax,
			panel.brushCoords.ay,
			panel.pressureSize2,
			panel.pressureOpacity2
		)
		-- appendStrokeLinePoint(stroke,
		--   panel.brushCoords.ax,panel.brushCoords.ay,
		--   panel.brushCoords.ax,panel.brushCoords.ay,
		--   panel.brushCoords.ax,panel.brushCoords.ay,
		--   panel.pressureSize2,panel.pressureOpacity2
		-- )

		startVerts()

		renderStroke(stroke, layer, frame, cat, false, panel.brush.paint)

		popStrokeLinePoint(stroke)
		-- popStrokeLinePoint(stroke)

		if vBufferN > 1 then
			if not panel.strokePreview and panel.stashedStrokePreview then
				panel.strokePreview = panel.stashedStrokePreview
			end
			if not panel.strokePreview or vBufferN + 64 > panel.strokePreview:getVertexCount() then
				print(
					"newmesh?",
					not panel.strokePreview,
					panel.strokePreview and vBufferN + 64 > panel.strokePreview:getVertexCount()
				)
				print(vBufferN + 64, panel.strokePreview and panel.strokePreview:getVertexCount())
				-- print("newmesh",time,panel.strokePreview,vBufferN,panel.strokePreview and panel.strokePreview:getVertexCount())
				if panel.strokePreview then
					panel.strokePreview:release()
				end
				panel.strokePreview = gfx.newMesh(vformat, vBufferN + 100000, "triangles")
				panel.stashedStrokePreview = panel.strokePreview
			end
			panel.strokePreview:setDrawRange(1, vBufferN)
			panel.strokePreview:setVertices(vBuffer, 1, vBufferN) --,nil,nil,true)
			-- panel.strokePreview:setTexture(load_imageTex("gradient"))
		end
		panel.previewStroke.groupIndex = lume.find(game.vectorLayers, panel.previewStroke.group)
	-- print("PREVIEW STROOK DONE")
	elseif restroke then
		local frame = frame
		if selection then
		-- print("gen mesh from selected strokes")
		else
			frame.released = false
		end
		-- table.sort(strokes,groupSort)
		local totalLength1, totalLength2 = 0, 0
		startVerts()
		local grouped = {}
		local groupedFrames = {}
		bench.push("sortstrokes")
		for g, group in ipairs(game.vectorLayers) do
			for s, stroke in ipairs(strokes) do
				if
					stroke.group == group
					and not (
						(group == "rough" or group == "note")
						and panel.exportFormats.noRoughNotes
						and panel.exporting
					)
				then
					if panel.forceRestroke ~= stroke or selection then
						stroke.index = s
						if selection and panel.selection.strokeFrames then
							table.insert(groupedFrames, panel.selection.strokeFrames[s])
						end
						stroke.groupIndex = g
						table.insert(grouped, stroke)
					end
				end
			end
		end
		bench.pop("sortstrokes")

		local groupIndex = 0
		groupCounts = {}
		for s, stroke in ipairs(grouped) do
			local z = 0.1
			if selection then
				if panel.strokeSelected.dragging then
					layer = panel.layers[panel.strokeSelected.layer]
					frame = layer.frames[panel.strokeSelected.frame]
				else
					layer = panel.selection.strokeFrames and panel.layers[groupedFrames[s][2]] or panel.l
					frame = panel.selection.strokeFrames and layer.frames[groupedFrames[s][1]] or panel.f
				end
			end

			if stroke.group == "fill" then
				local r, g, b, a = 0, 0, 0, 1
				r, g, b, a = unpackColor(stroke.color)
				a = a or 1
				if panel.debugView and panel.debugBoolean then
				else
					if not stroke.tris then
						local t, n = triangulateFill(stroke, getFillCat(panel))
						if n then
							stroke.tris = ffi.new("float[?]", n * 6)
							stroke.numtris = n
							ffi_copy(stroke.tris, t, ffi.sizeof("float") * n * 6)
						end
					end
					local tris, numtris = stroke.tris, stroke.numtris

					if tris then
						for t = 0, numtris * 6 - 6, 6 do
							vBufInsert(tris[t + 0], tris[t + 1], z, 0, 0, 0, r, g, b, a)
							vBufInsert(tris[t + 2], tris[t + 3], z, 0, 0, 0, r, g, b, a)
							vBufInsert(tris[t + 4], tris[t + 5], z, 0, 0, 0, r, g, b, a)
						end
					else
						frame.failedStrokes = frame.failedStrokes or {}
						table.insert(frame.failedStrokes, stroke.index)
						print("failedStroke frame", frame.id)
					end
				end
			else
				if not stroke.preRemove then
					local length = renderStroke(stroke, layer, frame, cat)
					totalLength1 = totalLength1 + stroke.line[0][0]
					totalLength2 = totalLength2 + (length or 0)
				end
			end

			if s == #grouped or grouped[s + 1].groupIndex ~= groupIndex then
				groupIndex = stroke.groupIndex
				groupCounts[groupIndex] = vBufferN + 1
			end
		end

		-- print("totalLengths",totalLength1,totalLength2,vBufferN)
		-------------------------------------------
		groupCounts[-1] = vBufferN
		local vertCount = vBufferN
		local format, mode = vformat, "triangles"
		local mesh

		if selection then
			panel.selection.strokeMesh = gfx.newMesh(format, max(vertCount + 64, 1), mode)
			if panel.strokeSelected.dragging then -- only effects live transform, aka never
				layer.strokeSelected = panel.selection.strokeMesh
			end
			mesh = panel.selection.strokeMesh
		else
			layer.strokeMeshes[peel] = layer.strokeMeshes[peel] or gfx.newMesh(format, max(vertCount + 64, 1), mode)
			mesh = layer.strokeMeshes[peel]
		end

		local vcount = mesh:getVertexCount()

		-- TODO remove vertCount==0
		if (vertCount + 64 > vcount) and not selection then
			layer.strokeMeshes[peel]:release()
			layer.strokeMeshes[peel] = gfx.newMesh(format, vertCount + 64, mode)
			mesh = layer.strokeMeshes[peel]
		end

		-- mesh:setTexture(load_imageTex("gradient"))

		mesh:setDrawRange(1, vertCount)
		if vertCount > 0 then
			-- print("====================================")
			-- print("if type(verts)",ffi.istype("strkvec",vBuffer))
			-- print("====================================")
			mesh:setVertices(vBuffer, 1, vertCount)
		end
	end

	if not selection then
		return groupCounts
	end
end
