local tmpBez = mth.newBezierCurve(0, 0, 0, 0, 0, 0, 0, 0)
local cos = math.cos
local sin = math.sin

function editor_cantDraw(panel)
	if not (panel.l.visible or panel.solo == panel.layer) and editor.stageHovered then
		editor_setToolTip("Layer Hidden", { x = screenW / 2, y = screenH / 2, time = 2 })
	end
	if panel.currentFrame > panel.l.length then
		editor_tempTip("No Frame!")
		-- editor_setToolTip("No Frame",{x=screenWidth/2,y=screenHeight/2,time=2})
	end
	return ctx.namer.enabled
		or not panel.f
		or ctx.namer.enabled
		or mouse.x == nil
		or mouse.y == nil
		or editor.state == "Context Menu"
		or contextMenu.items == 1
		or editor.state == "Panning"
		or panel.currentFrame > panel.l.length
		or not (panel.l.visible or panel.solo == panel.layer)
end

function editor_drawingTools(panel)
	gfx.push("all")
	if editor_cantDraw(panel) then
		gfx.pop()
		return
	end

	-- if keypressed("lshift") and not panel.clickedCanvas then
	--  if panel.tool ~= "move" then
	--    panel.prevTool = panel.tool
	--    panel.tool = "move"
	--  else

	--  end
	-- end

	if not panel.clickedCanvas then
		local wasPickingColor = panel.pickingColor
		panel.pickingColor = altDown and not shiftDown and (panel.tool == "brush" or panel.tool == "fill")
		if panel.pickingColor then
			mouse_setR(function()
          print("COLOR PICKer")
				if panel.color == panel.fillColor then
					editor_openColorTip(panel, "fillColor")
				else
					editor_openColorTip(panel.brush, "color")
				end
			end)
		elseif wasPickingColor then
			panel.wasPickingColor = true
		end
	end
  
	local prepared = editor_prepareToolMouse(panel)
  
  trace(
    string.format(
      " > stage size: %i,%i",
      panel.symbol.stageWidth,
      panel.symbol.stageHeight
    )
  )

	if panel.brushCoords.ax then
		if panel.l.format == "vector" then
			trace(
				string.format(
					" > brush coords: %i,%i (%i,%i)",
					panel.brushCoords.ax + panel.symbol.stageWidth / 2,
					panel.brushCoords.ay + panel.symbol.stageHeight / 2,
					panel.brushCoords.ax,
					panel.brushCoords.ay
				)
			)
		elseif panel.l.format == "raster" then
			trace(
				string.format(
					" > brush coords: %i,%i (%i,%i)",
					panel.brushCoords.x,
					panel.brushCoords.y,
					panel.brushCoords.ax,
					panel.brushCoords.ay
				)
			)
		end
	end

	if not prepared then
		gfx.pop()
		mouse_pushUp(function()
			editor_releaseTool(panel)
		end)
		return
	end

	if input_keyDown("space") then
		mouse_setCursor(cursors.hand)
	else
		if input_keyReleased("space") then
			mouse_setCursor()
		end
		if editor.stageHovered and not editor.colorTip.open then
			editor_toolPreview(panel)
			-- if not (altDown and cmdDown) and (panel.tool == "move" and not panel.selection.mesh) and (not panel.strokeSelected.near) then
			--  if (level.isAnim or editor.entityHovered == panel.instance) and not editor.gizmo.hovered then
			--    mouse_set(function()
			--      -- panel.pushUndoState()
			--      -- editor_startDrag(panel.transformer)
			--    end)
			--  end
			-- else
			if not input_anyKeyDown({ "up", "left", "down", "right", "1", "2" }) then
				mouse_set(function()
					editor_startStroke(panel)
				end)
			end
			-- end
		end
	end

	if panel.clickedCanvas == true then
		editor_prepareTool(panel)

		if panel.pickingColor then
			editor_pickColor(panel)
		elseif panel.tool == "brush" then
			editor_brushTool(panel)
		elseif panel.tool == "erase" then
			editor_eraserTool(panel)
		elseif panel.tool == "fill" then
			fill_fillTool(panel, true)
		elseif panel.tool == "move" then
			editor_moveTool(panel)
		elseif panel.tool == "select" then
			editor_selectionTool(panel)
		end

		panel.brushPrevPrev = deepcopy(panel.brushPrev)
		panel.brushPrev = deepcopy(panel.brushCoords)

    -- print("pushup release")
		mouse_pushUp(function()
			editor_releaseTool(panel)
		end)

		gfx.setShader()
		gfx.setLineWidth(2)
		panel.drawn = panel.drawn + 1
		-- panel.recentColors={}
	end

	-- if panel.tool == "move" and keyreleased("lshift") and not panel.clickedCanvas then
	--  panel.tool = panel.prevTool
	-- end

	gfx.pop()
end

function editor_releaseTool(panel)
	lwindow.setVsync(true)
  print("RELEASE TOOL")

	if panel.tool == "select" then
		editor_selectStrokes(panel)
	else
    print("RELEASE TOOL2")
		editor_finishStroke(panel)
    print("RELEASE TOOL3")
	end

	--panel.f.ox = mf(panel.f.ox)
	--panel.f.oy = mf(panel.f.oy)
	panel.axisLocked = false
	panel.selectingRect = false
	panel.clickedCanvas = false
  print("RELEASE TOOL4",panel.clickedCanvas)
	panel.drawTime = 0
	panel.drawn = 0
	panel.stroke = nil
	panel.strokeSelected.dragging = false
	panel.strokeSelected.strokeMesh = nil

	if panel.l.format == "vector" then
		-- if multicheckOR(panel.tool,{"brush","erase","fill","select"}) then
		if panel.strokePreview then
			panel.strokePreview:setDrawRange(1, 1)
		end
		if panel.tool == "erase" then
			print("panel.eraserRemovals", #panel.eraserRemovals)
			table.sort(panel.eraserRemovals, function(a, b)
				return a[2] > b[2]
			end)
			for r, rem in ipairs(panel.eraserRemovals) do
				local frame = rem[1]
				local s = rem[2]
				local stroke = frame.strokes[s]
				if s <= frame.startStrokeCount then
					table.insert(panel.strokeDiffs.removed, { frame = frame, stroke = stroke })
				end
				table.remove(frame.strokes, s)
				print("remove", s, #frame.strokes)
			end
			fill_prepareSegments(panel)
			if panel.eraseArea then
				editor_eraseLines(panel)
			end
		end
		if panel.tool == "brush" and not panel.brush.paint then
			table.insert(panel.strokeDiffs.added, { frame = panel.f, stroke = panel.f.strokes[#panel.f.strokes] })
		end
		if panel.strokeDiffs and #panel.strokeDiffs.added + #panel.strokeDiffs.removed > 0 then
			print("STROKES ADDED/REMOVED----------------")
			print(#panel.strokeDiffs.added, #panel.strokeDiffs.removed)

			panel.pushUndoData({
				strokesRemoved = copyStrokeDiffs(panel.strokeDiffs.removed),
				strokesAdded = copyStrokeDiffs(panel.strokeDiffs.added),
			})
		end
		-- end

		editor_updateStrokeBounds(panel)

		editor_refreshStrokes()

		if #panel.f.strokes == 0 then
			print("empty frame")
			editor_tempTip("Empty Frame")
		end
	end

	panel.tool = not shiftDown and panel.startTool or panel.tool
	panel.startTool = nil
end

function editor_toolPreview(panel)
	if editor.stageHovered then
		if input_keyDown("space") then
			mouse_setVisible(true)
		else
			if panel.pickingColor and (panel.tool == "brush" or panel.tool == "fill") then
        editor_updateLoupe()
			elseif panel.tool == "erase" or panel.tool == "brush" then
				if
					panel.brushCoords.x < 0
					or panel.brushCoords.y < 0
					or panel.brushCoords.x > panel.f.w
					or panel.brushCoords.y > panel.f.h
				then
				end
				local mode = panel.tool == "erase" and (panel.eraseMode == "brush" and "brush" or "erase") or "brush"
				editor_brushPreview(panel, mode)
			elseif panel.tool == "move" then
				editor_preselectSymbols(panel)
				editor_preselectStrokes(panel)
			elseif panel.tool == "fill" then
				editor_brushPreview(panel, "fill")
			elseif panel.tool == "select" then
				if not panel.selection.preTransform then
					mouse_setCursor(cursors.lasso)
				end
			end
		end
	end
end

function editor_filterLayer(panel, layer, filter, instance)
	local cursym = not instance or panel.instance == instance
	if layer.frames[layer.currentFrame] then
		if layer.format == "vector" then
			if not cursym or panel.solo == 0 then
				return not filter or (filter == "visible" and layer.visible) or layer.selected
			end
		else
			return cursym and panel.solo == layer.index
		end
	end
	return false
end

function editor_filterStrokeGroup(panel, stroke, group)
	return (group == "all pen" or stroke.group == panel.pen.group)
end

function editor_processStrokes(panel, func, filter)
	for l, layer in ipairs(panel.layers) do
		if editor_filterLayer(panel, layer, filter) then
			for s, stroke in ipairs(layer.frames[layer.currentFrame].strokes) do
				func(stroke)
			end
		end
	end
end

function editor_updateStrokeBounds(panel)
	editor_processStrokes(panel, shrinkStrokeLine)
	editor_processStrokes(panel, editor_strokeBounds)
end

function editor_strokeBounds(stroke, pad)
	local pad = pad or 0
	if stroke.debug or not stroke.line[1] then
		return false
	end
	local min, max = math.min, math.max
	local line = stroke.group == "fill" and stroke.line[1] or stroke.line
	stroke.bbox = stroke.bbox or ffi.new("float[5]")

	if not line[1][1] then
		if lfs.isFused() then
			print("invalid line")
		else
			error("invalid line")
		end
	end
	local bbox = stroke.bbox
	bbox[1], bbox[2], bbox[3], bbox[4] = line[1][1], line[1][2], line[1][1], line[1][2]
	for i = 1, line[0][0], 1 do
		-- print(line[i][1],line[i][2],line[i][3],line[i][4],line[i][5],line[i][6])
		local ctrl = (line[i][3] ~= 0 and line[i][5] ~= 0)
		bbox[1] = ctrl and min(bbox[1], line[i][1], line[i][3], line[i][5]) or min(bbox[1], line[i][1])
		bbox[2] = ctrl and min(bbox[2], line[i][2], line[i][4], line[i][6]) or min(bbox[2], line[i][2])
		bbox[3] = ctrl and max(bbox[3], line[i][1], line[i][3], line[i][5]) or max(bbox[3], line[i][1])
		bbox[4] = ctrl and max(bbox[4], line[i][2], line[i][4], line[i][6]) or max(bbox[4], line[i][2])
		-- print(" bbox", bbox[1], bbox[2], bbox[3], bbox[4])
	end
	bbox[1] = bbox[1] - pad
	bbox[2] = bbox[2] - pad
	bbox[3] = bbox[3] + pad
	bbox[4] = bbox[4] + pad
	return stroke.bbox
end

function strokeSelector(panel, strokes)
	for s, stroke in ipairs(layer.frames[layer.currentFrame].strokes) do
		if (stroke.bbox or editor_strokeBounds(stroke)) and pointInBBarray(panel.brushCoords, stroke.bbox) then
			if stroke.group == "fill" then
				if fill_pointInFill(panel, stroke, mouse) then
					selected.layer = l
					selected.frame = layer.currentFrame
					selected.s = s
					selected.stroke = stroke
					mouse.closest = 0
					break
				end
			else
				local line = stroke.line
				for i = 1, line[0][0] - 1, 1 do
					if
						distPointToLineSegment2(
							panel.brushCoords.x,
							panel.brushCoords.y,
							line[i][1],
							line[i][2],
							line[i + 1][1],
							line[i + 1][2]
						) < 10
					then
						tmpBez:setControlPoint(1, line[i][1], line[i][2])
						tmpBez:setControlPoint(2, line[i][5], line[i][6])
						tmpBez:setControlPoint(3, line[i + 1][3], line[i + 1][4])
						tmpBez:setControlPoint(4, line[i + 1][1], line[i + 1][2])
						local x1, y1, x2, y2
						local n = 5 + math.floor(math.dist(line[i][1], line[i][2], line[i + 1][1], line[i + 1][2]) / 50)
						-- trace(n)
						for t = 0, n, 1 do
							x2, y2 = tmpBez:evaluate(t / n)
							if t > 0 then
								local dist =
									distPointToLineSegment2(panel.brushCoords.x, panel.brushCoords.y, x1, y1, x2, y2)
								if dist < mouse.closest then
									mouse.closest = dist
									selected.layer = l
									selected.frame = layer.currentFrame
									selected.s = s
									selected.stroke = stroke
									selected.point = i
									local d1 = math.dist(x1, y1, x2, y2)
									local d2 = math.dist(x1, y1, panel.brushCoords.x, panel.brushCoords.y)
									local weight = lerp((t - 1) / n, t / n, d2 / d1)
									selected.weight = weight
									-- selected.left  = line[i]
									-- selected.right = line[i+1]
									
								end
							end
							x1, y1 = x2, y2
						end
					end
				end
			end
			-- editor_pushDraw(
			-- gfx.setColor(1,0,math.max(0,(50-mouse.closest)/50),1)
			-- gfx.rectangle("line",stroke.bbox[1]+panel.root.stageWidth/2,stroke.bbox[2]+panel.root.stageHeight/2,stroke.bbox[3]-stroke.bbox[1],stroke.bbox[4]-stroke.bbox[2])
			-- )
		end
	end
end

function editor_preselectSymbols(panel)
	local mouse = { panel.brushCoords.x, panel.brushCoords.y }
	local obb = {}
	panel.symbolSelected = false
	for l, layer in ipairs(panel.layers) do
		if editor_filterLayer(panel, layer, panel.selectLayer) then
			for s, symbol in ipairs(layer.frames[layer.currentFrame].symbols) do
				-- obb[1],obb[2],
				-- if point_in_poly(obb,mouse) then
				editor_preselectStrokes(panel)
				-- end
			end
		end
	end
end

function editor_preselectStrokes(panel)
	if panel.selection.preTransform or panel.symbolSelected then
		return
	end
	panel.strokeSelected = panel.strokeSelected or { layer = 0, frame = 0, stroke = 0, point = 0 }
	local selected = panel.strokeSelected
	if not panel.strokeSelected.dragging then
		panel.strokeSelected.near = false
		local mouse = { panel.brushCoords.x, panel.brushCoords.y, closest = 9999999999999 }
		for l, layer in ipairs(panel.layers) do
			if editor_filterLayer(panel, layer, panel.selectLayer) then
			end
		end
		selected.closest = mouse.closest
		if mouse.closest < 5 then
			selected.near = true
			trace(
				"selecting",
				selected.layer,
				selected.frame,
				selected.s,
				selected.point,
				selected.closest,
				selected.near
			)
		end
	end

	mouse_setVisible(true)
	if not panel.selection.preTransform then
		if selected.dragging or selected.near then
			if selected.stroke.group == "fill" then
				mouse_setCursor(cursors.move)
			else
				mouse_setCursor(cursors.curve)
			end
		else
			mouse_setCursor()
		end
	end
end

local function brushPreview(x, y, size, color, cursor)
	gfx.push("all")
	gfx.setBlendMode("alpha")
	gfx.translate(x, y)

	local r, g, b, a = unpackColor(color)
	gfx.setColor(r, g, b, a or 1)
	-- gfx.circle("fill",0, 0, size)
	gfx.setLineWidth(1)
	gfx.setColor(1, 1, 1, 1)
	-- gfx.circle("line",0, 0, size-1)
	-- gfx.arc("line","open", x,y, size, 0,pi,size+8)
	gfx.setColor(0, 0, 0, 1)
	gfx.circle("line", 0, 0, size)
	if cursor then
		-- gfx.circle("fill",0,0,10000)
	end
	-- gfx.arc("line","open", x,y, size, pi,2*pi,size+8)
	-- gfx.setColor(r,g,b,a)
	-- if panel.tool~="erase" then
	--  gfx.circle ("fill", 0, 0, math.min(size,4))
	-- end
	gfx.pop()
end

function editor_brushPreview(panel, mode)
	if mouse_getRelativeMode() then
		return
	end
	-- trace("panel.pushedColor",panel.pushedColor)
	local siz = mode == "fill" and panel.fillRadius or editor_getBrushSize(panel) * panel.l.lineWidth
	local size = (siz / editor.panMult.x) / game.resolution

	if mode == "fill" and panel.fillRadius == 0 then
		mouse_setVisible(true)
		mouse_setCursor(cursors.fill)
	-- print("hi")
	elseif mode == "fill" then
		mouse_setVisible(false)
		editor_pushDraw(function()
  		brushPreview(
  			mouse.x / game.resolution,
  			mouse.y / game.resolution,
  			((panel.fillRadius * 0.5) / editor.panMult.x) / game.resolution,
  			panel.fillColor
  		)
    end)
	elseif mode == "erase" then
		mouse_setCursor(cursors.eraser)
	elseif mode == "brush" and size < 6 then
		cursors.crosshair = cursors.crosshair or mouse_newCursor(load_imageData("crosshair"), 7, 7)
		mouse_setCursor(cursors.crosshair)
	else
		local color = panel.brush.paint and panel.fillColor or panel.brush.color
		local refreshCursor = false
		cursors.circle = cursors.circle or mouse_newCursor(load_imageData("circleFill12"), size / 2, size / 2)

		if
			size < 256
			and size > 0
			and (
				(panel.wasDrawing and not panel.clickedCanvas)
				or panel.wasPickingColor
				or not cursors.circle
				or (mouse_getCursor() ~= cursors.circle)
				or math.abs((panel.previewSize or 0) - size) > 1
			)
		then
			-- print("REFRESH CURSORRRRR",time)
			if panel.wasPickingColor then
				panel.wasPickingColor = false
				panel.wasDrawing = true
			end
			panel.previewSize = size
			local r, g, b, a = unpackColor(color)
			panel.cursorData = gfx.newImageData(size, size)
			-- rl.ImageClearBackground(panel.cursorData.data,ffi.new("Color",0,0,0,0))
			-- for i=0,size/2,0.5 do
			-- rl.ImageDrawCircle(panel.cursorData.data,size/2,size/2,i or size/2-1,ffi.new("Color",0,0,0,255))
			cursors.circle = mouse_newCursor(panel.cursorData, size / 2, size / 2)
			refreshCursor = true
			mouse_setCursor(cursors.circle)
			-- panel.wasDrawing=false
		end

		if not panel.clickedCanvas then
			if panel.wasDrawing then
				panel.wasDrawing = false
				mouse_setVisible(true)
				-- print("setcursorcircle1",time)
				mouse_setCursor(cursors.circle)
			end
		else
			if not panel.wasDrawing then
				-- panel.cursorData = gfx.newImageData(size+2,size+2)
				-- rl.ImageClearBackground(panel.cursorData.data,ffi.new("Color",0,0,0,0))
				-- cursors.circle   =   mouse_newCursor(panel.cursorData, size/2+1,size/2+1 )
				-- mouse_setCursor(cursors.circle)
			end
			size = size / 2
			panel.wasDrawing = true
			-- editor_pushDraw(function()
			-- brushPreview(mouse.x/game.resolution,mouse.y/game.resolution,size,color)
			-- end)
		end
	end
end

function editor_brushPreviewCanvas(panel)
	if mouse_getRelativeMode() then
		return
	end
	mouse_setVisible(false)
	gfx.setLineWidth(0.75)
	local r, g, b, a = unpackColor(panel.brush.color)
	gfx.setColor(r, g, b, a)

	local whichBrush = panel.tool == "erase" and panel.eraser.size
		or lerp(panel.sliders.size.min, panel.sliders.size.max, panel.sliders.size.pct)

	if panel.tool == "brush" then
		gfx.circle("fill", panel.brushCoords.x, panel.brushCoords.y, whichBrush * 0.5)
	end
	gfx.setLineStyle("rough")
	gfx.setColor(1, 1, 1, 1)
	gfx.arc("line", "open", panel.brushCoords.x, panel.brushCoords.y, whichBrush * 0.5, pi, 0, 20)
	gfx.setColor(0, 0, 0, 1)
	gfx.arc("line", "open", panel.brushCoords.x, panel.brushCoords.y, whichBrush * 0.5, 0, -pi, 20)
end

function editor_moveTool(panel)
	panel.brushDiff = {
		x = panel.brushCoords.x - panel.brushStart.x,
		y = panel.brushCoords.y - panel.brushStart.y,
		ax = panel.brushCoords.ax - panel.brushStart.ax,
		ay = panel.brushCoords.ay - panel.brushStart.ay,
	}

	if panel.selection.mesh then
		if panel.movingSelection then
			local f = panel.l.format == "raster" and mr or returnValue
			panel.selection.position = {
				x = f(panel.selection.offset.x + panel.brushDiff.x),
				y = f(panel.selection.offset.y + panel.brushDiff.y),
			}
			for s, symbol in ipairs(panel.selection.symbols) do
				symbol.symbol.position = symbol.position + vec3(panel.brushDiff.x, panel.brushDiff.y, 0)
				-- symbol.symbol.scale =
			end
			-- editor_refreshStrokes()

			mouse_pushRelease(function()
				panel.rotatingSelection = false
				panel.selection.offset = { x = panel.selection.position.x, y = panel.selection.position.y }
			end)
		end
	else
		if panel.selectingRect then
			local minx, maxx =
				mf(math.min(panel.selectingRect[2].x, mouse.x)),
				mf(math.max(panel.selectingRect[2].x, mouse.x))
			local miny, maxy =
				mf(math.min(panel.selectingRect[2].y, mouse.y)),
				mf(math.max(panel.selectingRect[2].y, mouse.y))
			local w, h = maxx - minx, maxy - miny

			if w > 0 or h > 0 then
				panel.selectingRect[3],panel.selectingRect[4] = minx,miny
        panel.selectingRect[5],panel.selectingRect[6] = w,h
			end
		-- if cmdDown then
		-- panel.f.ang = panel.f.ang and panel.f.ang - round(panel.brushCoords.py-panel.brushPrev.py,2) or 0
		-- else
		-- panel.f.ox = panel.f.ox + round(panel.brushCoords.px - panel.brushPrev.px, 2)
		-- panel.f.oy = panel.f.oy + round(panel.brushCoords.py - panel.brushPrev.py, 2)
		else
			local dist = math.dist
			local selected = panel.strokeSelected

			if selected.dragging then
				vector_genVectorSelectionMesh(panel)
				local bx, by = panel.brushStart.ax, panel.brushStart.ay
				local t = selected.weight
				local falloff = 1 - (panel.strokeSelectFalloff or 0)
				local mid = 0
				local length = 0
				local sign = math.sign(t - 0.5)
				local tee = math.abs((t - 0.5) * 2) ^ 0.1
				local t2 = (tee * sign) / 2 + 0.5
				-- print("tee",t)
				for i = 1, selected.base[0][0] - 1, 1 do
					if i <= selected.point then
						mid = mid
							+ math.dist(
									selected.base[i][1],
									selected.base[i][2],
									selected.base[i + 1][1],
									selected.base[i + 1][2]
								)
								* (i == selected.point and t2 or 1)
					end

					length = length
						+ math.dist(
							selected.base[i][1],
							selected.base[i][2],
							selected.base[i + 1][1],
							selected.base[i + 1][2]
						)
				end

				local total = 0
				local plen = 0
				local w
				for i = 1, selected.base[0][0], 1 do
					local len = 0
					if i < selected.base[0][0] then
						len = math.dist(
							selected.base[i][1],
							selected.base[i][2],
							selected.base[i + 1][1],
							selected.base[i + 1][2]
						)
					end

					local from = selected.base[i]
					local to = selected.stroke.line[i]
					--w = math.max(0, ((1/(math.abs(total-mid)+70))^0.5)-0.1)*30

					if i == selected.point + 1 then
						w = math.max(
							0,
							(1 - utils.smoothstep(math.abs(total - mid) ^ (1 + falloff) + 0, 0, math.min(1000, length)))
								- 0.01
						)
						to[3], to[4] = from[3] + panel.brushDiff.x * w, from[4] + panel.brushDiff.y * w
					end
					total = total + plen / 3

					w = math.max(
						0,
						(
							1
							- utils.smoothstep(
								(math.abs(total - mid) + 10) ^ (1 + falloff) + 0,
								0,
								math.min(1000, length)
							)
						) - 0.01
					)
					to[1], to[2] = from[1] + panel.brushDiff.x * w, from[2] + panel.brushDiff.y * w
					total = total + len / 3

					if i == selected.point then
						w = math.max(
							0,
							(1 - utils.smoothstep(math.abs(total - mid) ^ (1 + falloff) + 0, 0, math.min(1000, length)))
								- 0.01
						)
						to[5], to[6] = from[5] + panel.brushDiff.x * w, from[6] + panel.brushDiff.y * w
					end
					total = total + len / 3

					plen = len
				end

				local line = selected.stroke.line
				for i = 1, line[0][0], 1 do
					local prevp = line[math.max(1, i - 1)]
					local nextp = line[math.min(line[0][0], i + 1)]
					local point = selected.stroke.line[i]
					local d1 =
						math.dist(selected.base[i][1], selected.base[i][2], selected.base[i][3], selected.base[i][4])
					local d2 =
						math.dist(selected.base[i][1], selected.base[i][2], selected.base[i][5], selected.base[i][6])

					local tx, ty = nextp[1] - prevp[1], nextp[2] - prevp[2]
					local len = math.sqrt(tx * tx + ty * ty)
					tx, ty = tx / len, ty / len

					if i == selected.point then
						local tx, ty = point[5] - point[1], point[6] - point[2]
						local len = math.sqrt(tx * tx + ty * ty)
						local d3 = 1 / 2 --(math.abs(d2-len)+1)
						point[3], point[4] = point[1] - (tx / len) * d1 * d3, point[2] - (ty / len) * d1 * d3
					else
						if i == selected.point + 1 then
							local tx, ty = point[3] - point[1], point[4] - point[2]
							local len = math.sqrt(tx * tx + ty * ty)
							local d3 = 1 / 2 --(math.abs(d1-len)+1)
							point[5], point[6] = point[1] - (tx / len) * d2 * d3, point[2] - (ty / len) * d2 * d3
						else
							point[3], point[4] = point[1] - tx * d1, point[2] - ty * d1
							point[5], point[6] = point[1] + tx * d2, point[2] + ty * d2
						end
					end
				end
			end
		end
	end

	panel.brushPrev = deepcopy(panel.brushCoords)
end

function editor_selectionTool(panel)
	-- editor_resetBrushCanvas (panel)

	if not panel.selection.shape then
		return
	end

	local p = { editor_getBrushSize(panel), panel.brush.spacing, panel.brush.jitter }
	panel.brush.size, panel.brush.spacing, panel.brush.jitter = 0.1, 0, 0
	editor_curveStroke(panel)
	panel.brush.size, panel.brush.spacing, panel.brush.jitter = p[1], p[2], p[3]
end

local function trans(transform, tab, k, i, j, start)
	local res = 1
	local c, s = cos(transform.angle), sin(transform.angle)
	if start then
		tab[k][i] = tab[k][i] - (transform.bbox[1] + transform.bbox.w / 2) * res
		tab[k][j] = tab[k][j] - (transform.bbox[2] + transform.bbox.h / 2) * res
	else
		tab[k][i] = tab[k][i] * transform.scale.x
		tab[k][j] = tab[k][j] * transform.scale.y
		tab[k][i], tab[k][j] = c * tab[k][i] - s * tab[k][j], s * tab[k][i] + c * tab[k][j]
		tab[k][i] = tab[k][i] + (transform.position.x or 0) * res
		tab[k][j] = tab[k][j] + (transform.position.y or 0) * res
	end
end

function editor_adjStroke(stroke, transform, start)
	for s, line in ipairs(stroke.group == "fill" and stroke.line or { stroke.line }) do
		for i = 1, line[0][0], 1 do
			trans(transform, line, i, 1, 2, start)
			if line[i][3] ~= 0 then
				trans(transform, line, i, 3, 4, start)
			end
			if line[i][5] ~= 0 then
				trans(transform, line, i, 5, 6, start)
			end
		end
	end
end

function editor_eraserTool(panel)
	if panel.l.format == "vector" then
		if panel.eraseMode == "brush" then
			editor_brushTool(panel)
		elseif not panel.eraseArea and editor_eraseLines(panel) then
			editor_refreshStrokes(nil, true)
		end
	else
		panel.f.dirty = true
		panel.eraser.size = 20
		gfx.push("all")
		gfx.setCanvas(panel.f.canvas)
		gfx.setLineWidth(panel.eraser.size)
		gfx.setBlendMode("replace")
		gfx.setColor(0, 0, 0, 0)
		gfx.line(panel.brushPrev.x, panel.f.h - panel.brushPrev.y, panel.brushCoords.x, panel.f.h - panel.brushCoords.y)
		gfx.circle("fill", panel.brushCoords.x, panel.f.h - panel.brushCoords.y, panel.eraser.size * 0.5)
		panel.brushPrev = deepcopy(panel.brushCoords)
		gfx.pop()
	end
end

function editor_eraseLines(panel)
	local mull = catmullLine(mouse.line, 3)
	if #mull < 4 then
		return
	end

	bench.push("eraseLines")

	local eraserBox = {
		math.min(panel.brushPrevPrev.ax, panel.brushPrev.ax, panel.brushCoords.ax),
		math.min(panel.brushPrevPrev.ay, panel.brushPrev.ay, panel.brushCoords.ay),
		math.max(panel.brushPrevPrev.ax, panel.brushPrev.ax, panel.brushCoords.ax),
		math.max(panel.brushPrevPrev.ay, panel.brushPrev.ay, panel.brushCoords.ay),
	}

	local segBox = {}
	local cutAny = false

	local function intSpect(ints)
		local str = ""
		for i, int in ipairs(ints) do
			str = str .. (i > 1 and "," or "") .. tostring(#int)
		end
		return str
	end

	for l, layer in ipairs(panel.layers) do
		if editor_filterLayer(panel, layer, panel.selectLayer) then
			local frame = layer.frames[layer.currentFrame]
			for s = #frame.strokes, 1, -1 do
				-- print(s)
				local stroke = frame.strokes[s]
				local line = stroke.line
				local erase = (panel.eraseGroup == "all pen" or stroke.group == panel.pen.group)
					and not stroke.preRemove

				if stroke.group == "line" and erase and line[0][0] > 1 and bboxCollide1(eraserBox, stroke.bbox) then
					local segCut = false
					local strokeInts = panel.eraseMode == "segment" and panel.eraserSegs and panel.eraserSegs[s]
					for i = 1, line[0][0] - 1, 1 do
						local j = i + 1
						local x1, y1 = intersect(
							panel.brushPrev.ax,
							panel.brushPrev.ay,
							panel.brushCoords.ax,
							panel.brushCoords.ay,
							line[i][1],
							line[i][2],
							line[j][1],
							line[j][2]
						)
						if not x1 then
							x1, y1 = intersect(
								panel.brushPrevPrev.ax,
								panel.brushPrevPrev.ay,
								panel.brushPrev.ax,
								panel.brushPrev.ay,
								line[i][1],
								line[i][2],
								line[j][1],
								line[j][2]
							)
						end

						if x1 then
							cutAny = true

							if panel.eraseMode == "stroke" then
								table.insert(panel.strokeDiffs.removed, { frame = frame, stroke = stroke })
								table.remove(frame.strokes, s)
								break
							elseif panel.eraseMode == "segment" then
								if strokeInts then
									local t = math.dist(line[i][1], line[i][2], x1, y1)
										/ math.dist(line[i][1], line[i][2], line[j][1], line[j][2])
									-- check what stroke split points the erasers intersected point is between
									table.insert(strokeInts, 1, { line[1][1], line[1][2], 0, 1 })
									table.insert(
										strokeInts,
										{ line[line[0][0]][1], line[line[0][0]][2], 1, line[0][0] }
									)

									-- if cut is between sints, mark them as ends
									for si = 1, #strokeInts - 1, 1 do
										local sint1, sint2 = strokeInts[si], strokeInts[si + 1]
										if
											(i > sint1[4] or (i == sint1[4] and t > sint1[3]))
											and (i < sint2[4] or (i == sint2[4] and t < sint2[3]))
										then
											segCut = true
											sint1[5] = true
											sint2[5] = true
										end
									end
								else
									stroke.preRemove = true
									table.insert(panel.eraserRemovals, { frame, s })
								end
							elseif panel.eraseMode == "circle" then
							end
						end
					end

					if segCut then
						-- remove "ends" between actual ends and unused intersections
						-- ie xx__xxx_____xx -> x_x_xselectlayer
						print("")
						print("strokeInts", #strokeInts)
						local subStrokeInts = { {} }
						local endInts = strokeInts[1][5] and {} or { strokeInts[1] }
						for si = 2, #strokeInts - 1, 1 do
							local sint0, sint1, sint2 = strokeInts[si - 1], strokeInts[si], strokeInts[si + 1]
							if sint1[5] then
								if not (sint0[5] and sint2[5]) then
									table.insert(endInts, sint1)
								end
							else
								if sint0[5] then
									table.insert(subStrokeInts, {})
								end
								table.insert(subStrokeInts[#subStrokeInts], sint1)
							end
						end
						print("subStrokeInts", intSpect(subStrokeInts))

						if not strokeInts[#strokeInts][5] then
							table.insert(endInts, strokeInts[#strokeInts])
						end

						-- add new substrokes and remove the original
						-- don't forget to map the main stroke intersections onto the substrokes
						for si = 1, #endInts - 1, 2 do
							local sint1, sint2 = endInts[si], endInts[si + 1]
							local aStart, bEnd =
								(sint1[4] == 1 and sint1[3] == 0), (sint2[4] == line[0][0] and sint2[3] == 1)
							local a, b = aStart and sint1[4] or sint1[4] + 1, sint2[4]
							print("new sub", a, b, b - a)
							local subStroke = copyStroke(stroke, a, b)
							if not aStart then
								prependStrokeLinePoint(
									subStroke,
									sint1[1],
									sint1[2],
									sint1[1],
									sint1[2],
									sint1[1],
									sint1[2],
									subStroke.line[1][7],
									subStroke.line[1][8]
								)
							end
							if not bEnd then
								appendStrokeLinePoint(
									subStroke,
									sint2[1],
									sint2[2],
									sint2[1],
									sint2[2],
									sint2[1],
									sint2[2],
									subStroke.line[subStroke.line[0][0]][7],
									subStroke.line[subStroke.line[0][0]][8]
								)
							end

							print("addstroke", subStroke.line[0][0])
							editor_strokeBounds(subStroke)
							table.insert(panel.strokeDiffs.added, { frame = frame, stroke = subStroke })
							table.insert(frame.strokes, subStroke)
							local subInts = subStrokeInts[(si + 1) / 2]
							if subInts and #subInts > 0 then
								panel.eraserSegs[#frame.strokes] = subInts
							end
						end

						stroke.preRemove = true
						panel.eraserSegs[s] = nil
						table.insert(panel.eraserRemovals, { frame, s })

						-- table.insert(panel.strokeDiffs.removed,{frame=frame,stroke=stroke})
						-- table.remove(frame.strokes,s)

						-- if panel.debugView then
						--  for si,sint in ipairs(endInts) do
						--    editor_addTestLines({{color = {r=si/#endInts,g=0,b=0},
						--      sint[1]-5,sint[2]-5,
						--      sint[1]+5,sint[2]+5,
						--      sint[1]-5,sint[2]+5,
						--      sint[1]+5,sint[2]-5,
						--    }})
						--  end
						-- end
					end
				end
			end
		end
	end

	bench.pop("eraseLines")

	if not panel.eraseArea then
		if cutAny then
			mouse.line = {}
		elseif #mouse.line > 100 then
			table.remove(mouse.line, #mouse.line)
			table.remove(mouse.line, #mouse.line)
		end
	end

	return cutAny
end

function editor_sameSizeTexture(a, b)
	return a and b and a:getWidth() == b:getWidth() and a:getHeight() == b:getHeight()
end

function editor_prepareErase(panel)
	for l, layer in ipairs(panel.layers) do
		if editor_filterLayer(panel, layer, panel.selectLayer) then
			local frame = layer.frames[layer.currentFrame]
			for s = #frame.strokes, 1, -1 do
				if frame.strokes[s].debug then
					table.remove(frame.strokes, s)
				end
			end
			frame.startStrokeCount = #frame.strokes
		end
	end
	editor_refreshStrokes()
end

function editor_startVectorStroke(panel, size, color)
	if config.unlockDrawing then
		lwindow.setVsync(false)
	end
	local canv = ctx.viewport.canvas
	if not panel.strokePreviewUnderlay or not editor_sameSizeTexture(canv, panel.strokePreviewUnderlay) then
		panel.strokePreviewUnderlay = newCanvas(canv:getDimensions())
	end
	if not panel.strokePreviewCanvas or not editor_sameSizeTexture(canv, panel.strokePreviewCanvas) then
		animatePanel.strokePreviewCanvas = newCanvas(canv:getDimensions())
	end

	panel.f.strokes = type(panel.f.strokes) == "table" and panel.f.strokes or {}
	local size = editor_getBrushSize(panel)
	local ends = panel.brush.paint and "round" or panel.pen.ends
	local group = editor_getBrushGroup(panel)

	table.insert(
		panel.f.strokes,
		newStroke({
			size = size,
			ends = ends,
			group = group,
			color = color,
		})
	)

	panel.stroke = panel.f.strokes[#panel.f.strokes]

	panel.previewStroke = newStroke({
		size = size,
		ends = ends,
		group = group,
		color = color,
	})
	panel.tipDist = panel.l.format == "vector" and 0 or 1
	panel.tipDist2 = 0
	panel.f.preVerts = {}
	panel.drawTime = 1
	panel.strokeSmoothStart = 3
  print("START STROKE")
end

function editor_startStroke(panel)
	local brush_pen = panel.brush.paint and panel.brush or panel.pen
	local press = brush_pen.pressureSensitivity
	local size = brush_pen.pressureSizeEnabled
	local opacity = brush_pen.pressureOpacityEnabled
	panel.pressureSize1 = (press and size) and mouse.pressure or 1
	panel.pressureSize2 = (press and size) and mouse.pressure or 1
	panel.pressureOpacity1 = (press and opacity) and mouse.pressure or 1
	panel.pressureOpacity2 = (press and opacity) and mouse.pressure or 1

	panel.f.lastDrawn = os.time()
	panel.axisLocked = false
	panel.lastPressure = 0
	panel.strokeTime = 0


	if config.unlockDrawing and config.unlockMouse then
		lwindow.setVsync(false)
	end

	panel.strokeDiffs = { added = {}, removed = {} }

	if panel.tool == "brush" then
		if not altDown then
			panel.pushUndoState("Stroke")
		end
		if config.unlockDrawing then
			lwindow.setVsync(false)
		end

		local color = editor_applyBrushJitter(panel, true)

		if panel.l.format == "vector" then
			editor_startVectorStroke(panel, size, color)
		else
			panel.drawTime = 1
			editor_addToPalette(panel, panel.brush.color)
			gfx.reset()
			-- gfx.setDefaultFilter(panel.filter)
			-- if (not panel.l.autoExpand) and panel.f.blank and (panel.f.w<panel.stageWidth or panel.f.h<panel.stageHeight) then
			--  panel.f.ox,panel.f.oy=panel.f.w/2,panel.f.h/2
			--  editor_expandCanvas(panel, nil, {-panel.stageWidth/2,-panel.stageHeight/2,panel.stageWidth/2,panel.stageHeight/2,32})
			-- end
			panel.brushFormat = { format = "rgba8", msaa = 0 }

			if not editor_sameSizeTexture(panel.f.canvas, panel.brushCanvas) then
				panel.brushCanvas = newCanvas(panel.f.w, panel.f.h, panel.brushFormat)
			end
			gfx.setFilter(
				panel.brushCanvas,
				(panel.antiAlias and editor.viewport.camera.soom > 1) and "linear" or "nearest"
			)
			panel.brushCanvas:renderTo(function()
				gfx.clear(1, 1, 1, 0)
			end)
		end
	end

	if panel.tool == "depth" then
		panel.markDragging = -1
	end

	local shouldMove = (panel.tool == "move")
		or (panel.tool == "select" and (panel.hoveringSelection or panel.selection.preTransform))

	panel.movingSelection = false
	panel.justSelected = false

	if shouldMove then
		if panel.selection.mesh and (panel.hoveringSelection or panel.selection.preTransform) then
			print("panel.selection.preTransform", panel.selection.preTransform)
			local transformType = panel.selection.preTransform

			if not panel.selection.cut then
				panel.pushUndoState(transformType)
			end
			panel.selection.dontPaste = false
			if transformType == "Move" then
				panel.movingSelection = true
			elseif transformType == "Rotate" then
				panel.rotatingSelection = true
				panel.selection.startAngle = panel.selection.angle
				panel.selection.startRot = panel.selection.preRot
				panel.selection.scalePoint = panel.selection.transPoint
				panel.brushStart = { x = panel.brushCoords.x, y = panel.brushCoords.y }
			elseif transformType == "Scale" then
				panel.selection.startScale = lume.clone(panel.selection.scale)
				panel.selection.offset = lume.clone(panel.selection.position)
				panel.selection.scalePoint = panel.selection.transPoint
				panel.scalingSelection = panel.selection.transPoint
			end
			editor_cutStrokeSelection(panel)

			panel.pushUndoState(transformType)
			if panel.tool == "select" then
				panel.startTool = "select"
				panel.tool = "move"
			end
			panel.selection.offset = lume.clone(panel.selection.position)
		else
			editor_dropSelection(panel)
			local selected = panel.strokeSelected
			if selected.near then
				if selected.stroke.group == "fill" then
					panel.selection = {}
					panel.selection.format = "vector"
					panel.selection.strokes = { selected.stroke }
					panel.selection.strokeFrames = { { selected.frame, selected.layer } }
					panel.selection.frames = { panel.layers[selected.layer].frames[selected.frame] }
					panel.selection.bbox = {
						selected.stroke.bbox[1],
						selected.stroke.bbox[2],
						selected.stroke.bbox[3],
						selected.stroke.bbox[4],
						w = selected.stroke.bbox[3] - selected.stroke.bbox[1],
						h = selected.stroke.bbox[4] - selected.stroke.bbox[2],
					}
					panel.selection.mesh = RectMesh
					editor_refreshStrokes(selected.stroke)
					panel.sudoForceRestroke = true
					vector_genVectorSelectionMesh(panel)
					editor_registerSelection(panel)
				else
					panel.pushUndoState("Modify")
					selected.base = copyLine(selected.stroke.line)
					selected.dragging = true
					editor_refreshStrokes(selected.stroke)
					vector_genVectorFrameMesh(
						panel,
						panel.layers[selected.layer],
						panel.layers[selected.layer].frames[selected.frame]
					)
				end
			else
				panel.selectingRect = { lume.clone(panel.brushCoords), vec2(mouse.x, mouse.y) }
			end
		end
	else
		if panel.tool == "select" and not shouldMove then
			panel.pushUndoState("Select")
			editor_dropSelection(panel)
			editor_releaseSelection(panel)
			panel.selection = {}
			panel.selection.shape = {}
			panel.selection.verts = {}
			panel.selection.offset = {}
			if panel.selection.mesh then
				panel.selection.mesh:release()
				panel.selection.mesh = nil
			end
		end
	end

	if panel.tool == "erase" then
		panel.pushUndoState("Erase")
		if panel.l.format == "vector" then
			editor_updateStrokeBounds(panel)
			editor_prepareErase(panel)
			fill_prepareSegments(panel)
			panel.eraserRemovals = {}
			if panel.eraseMode == "brush" then
				editor_startVectorStroke(panel, size, { r = 1, g = 1, b = 1, a = 1, h = 1, s = 1, v = 1 })
			end
		end
	end

	if panel.tool == "fill" then
		if not altDown then
			panel.pushUndoState("Fill")
			panel.lastFilledShape = 1
			fill_fillTool(panel)
		end
	end

	panel.brushCoords.px = mouse.x * game.resolution
	panel.brushCoords.py = mouse.y * game.resolution
	editor_prepareToolMouse(panel)

	panel.brushStart = {
		x = panel.brushCoords.x,
		y = panel.brushCoords.y,
		ax = panel.brushCoords.ax,
		ay = panel.brushCoords.ay,
	}
	panel.diff = { x = 0, y = 0, w = 0, h = 0 }
	panel.clickedCanvas = true
	panel.brushPrev = deepcopy(panel.brushCoords)
end

function editor_cutStrokeSelection(panel, remove, linesonly)
	if not panel.selection.cut then
		-- move strokes to be relative to selection box, needed for correct pasting

		if panel.l.format == "vector" then
			print("CUTTTTTTTTTTTTTTTTTTT")
			panel.strokeDiffs = panel.strokeDiffs or { added = {}, removed = {} }

			if panel.selection.removals then
				table.sort(panel.selection.removals, function(a, b)
					return a.stroke_index > b.stroke_index
				end)
				for r, removal in ipairs(panel.selection.removals) do
					table.insert(
						panel.strokeDiffs.removed,
						{ frame = removal.frame, stroke = removal.frame.strokes[removal.stroke_index] }
					)
					table.remove(removal.frame.strokes, removal.stroke_index)
					print("REEEEEEEEEEEEEEEEEEEEEEEEEEEEEMOVE")
				end
			end

			local added = {}
			if panel.selection.additions then
				for a, addition in ipairs(panel.selection.additions) do
					print("addition.frame", a, addition.frame, addition.frame and addition.frame.id or 0)
					-- dont think i need this, might mess up undos/redos?
					table.insert(panel.strokeDiffs.added, { frame = addition.frame, stroke = addition.stroke })
					table.insert(addition.frame.strokes, addition.stroke)
					added[addition.stroke] = true
				end
			end

			-- CONTAINED
			local selected = {}
			for s, stroke in ipairs(panel.selection.strokes) do
				selected[stroke] = true
			end
			if panel.selection.frames then
				for f, frame in ipairs(panel.selection.frames) do
					for s = #frame.strokes, 1, -1 do
						local stroke = frame.strokes[s]
						if selected[stroke] then
							table.remove(frame.strokes, s)
							table.insert(panel.strokeDiffs.removed, { frame = frame, stroke = stroke })
						end
					end
				end
			end

			panel.sudoForceRestroke = true

			panel.selection.strokes = copyStrokes(panel.selection.strokes)
			for s, stroke in ipairs(panel.selection.strokes) do
				print("stroke pt 1- start", stroke.line[1][1], stroke.line[1][2])
				editor_adjStroke(stroke, panel.selection, true)
				print("stroke pt 1- after", stroke.line[1][1], stroke.line[1][2])
			end
			vector_genVectorSelectionMesh(panel)
		end

		if panel.l.format == "raster" then
			local black = newCanvas(1, 1)
			black:renderTo(function()
				gfx.clear(0, 0, 0, 0)
			end)

			panel.f.canvas:renderTo(function()
				gfx.setBlendMode("replace")
				gfx.setColor(0, 0, 0, 0)
				gfx.setTexture(panel.eraseMesh, black)
				gfx.draw(panel.eraseMesh, 0, panel.f.h, 0, 1, -1)
			end)
		end

		panel.selection.cut = true
	end

	if remove then
		panel.selection.strokeFrames = nil
		panel.sudoForceRestroke = true
	end

	return panel.selection.strokes
end

function editor_copyFrameContents(panel, cut)
	if panel.selection.selected then
		panel.selectionBuffer = deepcopy(panel.selection)
		panel.selectionBuffer.pasted = false
		panel.selectionBuffer.mesh = gfx.newMesh(zformat, panel.selection.verts, "triangles")
		panel.selectionBuffer.canvas = gfx.copyCanvas(panel.selection.canvas)
		panel.selectionBuffer.strokes = copyStrokes(panel.selection.strokes)
		for s, stroke in ipairs(panel.selectionBuffer.strokes) do
			editor_adjStroke(stroke, panel.selection, true)
		end
		panel.selection.frame = panel.frame
		if cut then
			panel.selection = {}
			editor_tempTip("Cut Selection")
		else
			editor_tempTip("Copy Selection")
		end
	end
end

function editor_cutFrameContents(panel)
	editor_copyFrameContents(panel, true)
end

function editor_pasteFrameContents(panel)
	if panel.selectionBuffer then
		panel.pushUndoState("Paste Selection")

		-- editor_pasteSelectionContents(panel)

		panel.tool = "move"

		-- local x,y,w,h = panel.selectionBuffer.quad:getViewport()
		-- local sw,sh=panel.selectionBuffer.quad:getTextureDimensions()
		panel.selection = deepcopy(panel.selectionBuffer)
		panel.selection.pasted = true
		if panel.selectionBuffer and panel.selectionBuffer.strokes then
			panel.selection.strokeFrames = nil
			panel.selection.cut = true
			for s, stroke in ipairs(panel.selectionBuffer.strokes) do
				stroke.id = simpleuuid()
			end
			editor_refreshStrokes()
		end

		log("diff", vec2(panel.selection.diff))
		log("pos ", vec2(panel.selection.position))

		if panel.frame ~= panel.selection.frame then
			panel.selection.position =
				{ x = panel.f.w / 2 + panel.selection.diff.x, y = panel.f.h / 2 + panel.selection.diff.y }
		end

		panel.selection.mesh = gfx.newMesh(zformat, panel.selectionBuffer.verts, "triangles")
		panel.selection.canvas = gfx.copyCanvas(panel.selectionBuffer.canvas)

		editor_tempTip("Paste Selection")
	end
end

function editor_moveToNewLayer(panel)
	editor_cutFrameContents(panel)
	editor_newLayer(panel, panel.layer + 1)
	editor_pasteFrameContents(panel)
end

function editor_dropSelection(panel, undoing)
	if panel.selection.selected then
		print("DROP SELECTION //////////////////////////////////////")
		print("   paste? ", panel.selection.dontPaste)
		if undoing then
			editor_pasteSelectionContents(panel, true)
		else
			-- if on "select region", don't increment undopos
			if panel.selection.dontPaste then
				print(#panel.undos - panel.undoPos, #panel.undos)
				print("dropped on", panel.undos[#panel.undos - panel.undoPos].action)
				panel.strokeDiffs = nil
				if panel.undos[#panel.undos - panel.undoPos].action == "Select Region" then
					panel.undoPos = panel.undoPos + 1
				else
					-- panel.undos = splice(panel.undos,1,#panel.undos-panel.undoPos+1)
					panel.undoPos = panel.undoPos - 1
				end
			else
				print("PUSH UNDO\n")
				panel.pushUndoState("Drop Selection")
				editor_pasteSelectionContents(panel, true)
			end
		end
		panel.selection = {}
	end
end

function editor_releaseSelection(panel)
	if panel.selection.canvas then
		panel.selection.canvas:release()
		panel.selection.canvas = nil
	end
	panel.selection.shape = {}
	if panel.selection.mesh then
		panel.selection.mesh:release()
		panel.selection.mesh = nil
	end
	panel.selection = {}
end

function editor_pasteSelectionContents(panel, noUndo)
	if not panel.selection.position then
		return
	end

	panel.pasted = true
	if panel.selection.strokes then
		local strokes = copyStrokes(panel.selection.strokes)
		for s, stroke in ipairs(strokes) do
			editor_adjStroke(stroke, panel.selection, false)
			editor_strokeBounds(stroke)
			local sf = panel.selection.strokeFrames
			stroke.cat = false
			if sf then
				table.insert(panel.layers[sf[s][2]].frames[sf[s][1]].strokes, stroke)
			-- table.insert(panel.strokeDiffs.added,{frame  = panel.layers[sf[s][2]].frames[sf[s][1]], stroke = stroke })
			else
				table.insert(panel.f.strokes, stroke)
				-- table.insert(panel.strokeDiffs.added,{frame  = panel.f, stroke = stroke })
			end
		end

		editor_refreshStrokes()
	end

	if not panel.selection.canvas then
		return
	end

	if not noUndo then
		panel.pushUndoState("Paste Selection")
	end
	-- editor_expandCanvas  (panel,true)
	if panel.selection.expanded then
		panel.selection.position = {
			x = panel.selection.position.x + panel.selection.expanded.x,
			y = panel.selection.position.y + panel.selection.expanded.y,
		}
	end
	panel.f.canvas:renderTo(function()
		editor_drawRasterSelection(panel)
	end)
end

function editor_drawRasterSelection(panel)
	if panel.selection.position then
		gfx.setTexture(panel.selection.shapeMesh, panel.selection.canvas)
		fill_copyFrameCanvasToTemp(panel)
		-- panel.tempCanvas:newImageData():encode("png","pre3.png")
		local temp = newCanvas(panel.f.w, panel.f.h)
		gfx.push("all")
		gfx.setColor(1, 1, 1, 1)
		gfx.setBlendMode("replace")
		gfx.setCanvas(temp)
		gfx.clear(0, 0, 0, 0)
		gfx.setShader(shaders.forwardRender)
		gfx.draw(
			panel.selection.shapeMesh,
			panel.selection.position.x,
			panel.f.h - panel.selection.position.y,
			panel.selection.angle,
			panel.selection.scale.x,
			panel.selection.scale.y,
			panel.selection.bbox.w / 2,
			panel.selection.bbox.h / 2
		)

		gfx.setCanvas(panel.f.canvas)
		-- temp:newImageData():encode("png","temp.png")
		gfx.setShader(shaders.alphaMultiply)
		gfx.setDepthMode("always")
		shaders.alphaMultiply:send("dest", panel.tempCanvas)
		gfx.draw(temp)
		gfx.pop()
	end
end

function editor_brushTool(panel)
	if panel.f.blank and panel.tool ~= "erase" then
		panel.f.blank = false
		panel.unblanked = true
	end
	if panel.l.format == "vector" then
		editor_curveStroke(panel)
	else
		if panel.brushCanvas then
			editor_expandCanvas(panel)
			editor_applyBrushJitter(panel, false)
			editor_resetBrushCanvas(panel)
			editor_curveStroke(panel)
		end
	end
end

function editor_prepareToolMouse(panel, pan)
	panel.brushCoords.px = easeTo(panel.brushCoords.px, mouse.x * game.resolution, panel.brushEase)
	panel.brushCoords.py = easeTo(panel.brushCoords.py, mouse.y * game.resolution, panel.brushEase)

	local model = panel.f --panel.instance.adata.layers[panel.layer].frames[panel.frame]

	if
		panel.l.type ~= "drawing"
		or not model
		or (panel.l.format == "raster" and (not model.tris or not model.tris[1]))
	then
		trace("No valid frame")
		return
	end

	--trace("Editing model")
	-- if not panel.tempTri then
	panel.tempTri = {
		vec3(0, 0, 1),
		vec3(0, 1, 1),
		vec3(1, 0, 1),
	}
	panel.ttri = {}
	-- end

	local tri

	if panel.l.format == "vector" then
		tri = panel.tempTri
	else
		if model.tris then
			tri = model.tris[1]
		else
			trace("no model.tris")
			return
		end
	end

	local sideA, sideB = tri[2] - tri[1], tri[3] - tri[1]
	local cross = vec3.cross(sideA, sideB)
  local transform = panel.instance.transform
	-- TODO opover
	local normal = transform * vec3(0, 0, 1) 

	-- if not panel.f.transform then
	--  if not applyTransform(panel.f) then
	--    log("fuggg")
	--    return
	--  end
	-- end
  local cam = ctx.viewport.camera
	-- TODO opover
	local pt = panel.instance.transform * (panel.f.transform or mat4()) * vec3(0) 
	cam.lookTarg = pt

	-- TODO opover
  animatePanel.drawingPosition = -(transform * vec3(0, 0, -vec3.dist(ctx.viewport.camera.pos, cam.lookTarg))) 

	panel.ttri[1] = transform * tri[1]
	panel.ttri[2] = transform * tri[2]
	panel.ttri[3] = transform * tri[3]

	local ttri = panel.ttri

	if not ctx.viewport.camera.ray then
		trace("no ctx.viewport.camera.ray")
		return
	end


	local intersect, dist = cpml.intersect.ray_plane(ctx.viewport.camera.ray, { position = ttri[1], normal = normal })
	local intersect2

	if pan then
		local x, y = mouse.x * game.resolution, (mouse.y) * game.resolution
		intersect =
			cpml.intersect.ray_plane(camera_cursorRay(x, y, panel.view), { position = ttri[1], normal = normal })
		intersect2 = cpml.intersect.ray_plane(
			camera_cursorRay(x + 1, y + 1, panel.view),
			{ position = ttri[1], normal = normal }
		)
	end

	--editor.testTri =gfx.newMesh(zformat,{{ttri[1].x,ttri[1].y,ttri[1].z,1,1,0},{ttri[2].x,ttri[2].y,ttri[2].z,1,1,1},{ttri[3].x,ttri[3].y,ttri[3].z,1,0,0}},"fan")
	--editor.testTri:setTexture(panel.instance.img)

	if panel.l.format == "vector" then
		panel.f.w = 1
		panel.f.h = 1
	end

	local prevCoords = deepcopy(panel.brushCoords)

    -- trace("")
  	-- trace("get brush coords")
    -- trace("ray",ctx.viewport.camera.ray.position,ctx.viewport.camera.ray.direction)
	  -- trace("pos",ttri[1] )
	  -- trace("normal",normal )
	  -- trace("int",intersect)
    -- if intersect2 then 
    --   trace("int",intersect)
    -- end

	if intersect then
		local w = vec3.dist(ttri[1], ttri[3])
		local h = vec3.dist(ttri[1], ttri[2])
		local x = vec3.dot((ttri[3] - ttri[1]), intersect - ttri[1])
		local y = vec3.dot((ttri[2] - ttri[1]), intersect - ttri[1])


		panel.mouse = intersect
		-- transform*
    -- trace(ttri[1],ttri[2],ttri[3])
    -- local int = vec3(mat4.mul_vec4({},mat4.invert(mat4(), transform),{intersect.x,intersect.y,intersect.z,1}))
    -- print(transform)
    -- trace("ttrin",intersect.x,intersect.y,intersect.z)
    -- trace("ttrin",int.x,int.y,int.z)

		if tostring((x / (w ^ 2))) == "nan" then
			trace("nan x")
			return false
		else
			local stabilizer = mc(2, mc(math.max(0, cam.soom - 1) * 2, panel.pen.stabilizer, 1) * 20, 99)
			-- trace("stabilizer",stabilizer)
			if panel.drawTime == 0 then
				stabilizer = 1
			end

			panel.brushCoords.ax = panel.axisLocked == "x" and panel.rawCoords.x + (math.random() - 0.5) * 0.01
				or easeTo(panel.brushCoords.ax, (x / w), stabilizer)
			panel.brushCoords.ay = panel.axisLocked == "y" and panel.rawCoords.y + (math.random() - 0.5) * 0.01
				or easeTo(panel.brushCoords.ay, (y / h), stabilizer)

			-- trace("panel.axisLocked",panel.axisLocked)
			-- trace("panel.drawn",panel.drawn)
			if shiftDown and panel.brush.paint and not panel.axisLocked and panel.drawn > 0 then
				panel.axisLocked = (mouse.trend.z > 0) and "y" or "x"
			end
			panel.rawCoords = vec2(panel.brushCoords.ax, panel.brushCoords.ay)

			if
				panel.drawTime > 1
				and prevCoords.ax == panel.brushCoords.ax
				and panel.brushCoords.ay == prevCoords.ay
			then
				return false
			end
			if intersect2 then
				local ix,iy=
          vec3.dot(ttri[3] - ttri[1], intersect2 - ttri[1]) / w - x / w,
					vec3.dot(ttri[2] - ttri[1], intersect2 - ttri[1]) / h - y / h
        return ix,iy
			end

			panel.brushCoords.x = easeTo(panel.brushCoords.x, (x / (w ^ 2)) * panel.f.w, stabilizer)
			panel.brushCoords.y = easeTo(panel.brushCoords.y, (y / (h ^ 2)) * panel.f.h, stabilizer)
			-- print(panel.brushCoords.x,panel.brushCoords.ax)
			-- if panel.debugView then
				-- trace("raw  ", w, h)
				-- trace("raw  ", x, y)
				-- trace("raw  ", (x / (w ^ 2)), (y / (h ^ 2)))
				-- trace("intrs", intersect.x, intersect.y, intersect.z)
				-- trace("cords", panel.brushCoords.x, panel.brushCoords.y)
				-- trace("curds", panel.brushCoords.ax, panel.brushCoords.ay)
				-- trace("dims ", panel.f.w, panel.f.h)
			-- end

			panel.brushCoords.angle = 0
			return true
		end
	else
		-- trace("nori",panel.instance.nextOrientation )
		-- trace("ori",panel.instance.orientation )
		-- trace("ray2",ctx.viewport.camera.ray.position, ctx.viewport.camera.ray.direction)
		-- trace("pos", ttri[1])
		-- trace("normal", normal)
		-- trace("no brush intersect")
		return false
	end
end

function editor_prepareTool(panel)
	if mouse.pressure ~= panel.lastPressure then
		panel.pressureSize1 = panel.pressureSize2 or 0
		panel.pressureOpacity1 = panel.pressureOpacity2 or 0

		local brush_pen = panel.brush.paint and panel.brush or panel.pen
		if brush_pen.pressureSensitivity and brush_pen.pressureSizeEnabled then
			panel.pressureSize2 = (1 - brush_pen.pressureSizeAmount) + brush_pen.pressureSizeAmount * mouse.pressure
		else
			panel.pressureSize2 = 1
		end

		if brush_pen.pressureSensitivity and brush_pen.pressureOpacityEnabled then
			panel.pressureOpacity2 = (1 - brush_pen.pressureOpacityAmount)
				+ brush_pen.pressureOpacityAmount * mouse.pressure
		else
			panel.pressureOpacity2 = 1
		end
	end
	panel.lastPressure = mouse.pressure

	-- print("mouse.pressure",mouse.pressure,panel.brush.pressureSensitivity,panel.brush.pressureSizeEnabled,panel.pressure1)
	panel.strokeTime = panel.strokeTime + 1
	if panel.brushPrev == nil or panel.drawn < 2 then
		panel.brushPrev = deepcopy(panel.brushCoords)
	end
	if panel.brushPrevPrev == nil or panel.drawn < 2 then
		panel.brushPrevPrev = deepcopy(panel.brushCoords)
	end

	local mouseDiff = { x = panel.brushCoords.x - panel.brushPrev.x, y = panel.brushCoords.y - panel.brushPrev.y }
	panel.brush.mouseAng = math.angle(panel.brushCoords.x, panel.brushCoords.y, panel.brushPrev.x, panel.brushPrev.y)
		- pi / 2
	panel.brush.mouseDist = math.ddist(mouseDiff.x, mouseDiff.y)

	panel.brushCoords.angle = panel.brush.mouseAng

	if panel.f.blank then
		if panel.blankAnim then
			panel.blankAnim = false
		--panel.instance:move (panel.brushCoords.x-50, panel.brushCoords.y-50)
		else
			--panel.f.ox = -panel.brushCoords.x
			--panel.f.oy = -panel.brushCoords.y

			-- mouse_pushRelease(function() editor_clipFrame(panel.f) end)
		end
		panel.f.blank = false
		panel.unblanked = true

		editor_prepareTool(panel)
	end
	-- gfx.push("all")
end

function editor_resetBrushCanvas(panel)
	if panel.l.format == "vector" then
		return
	end

	-- gfx.push("all")
	if not panel.brushCanvas then
		panel.brushCanvas = newCanvas(panel.f.w, panel.f.h, panel.brushFormat)
	end
	gfx.setShader()
	gfx.setCanvas(panel.brushCanvas)
	gfx.setColor(1, 1, 1, 1)
	gfx.setBlendMode("alpha", "premultiplied")
end

function editor_expandCanvas(panel, selection, bounds)
	if panel.l.format == "vector" and not (panel.tool == "fill" or panel.tool == "select") then
		return
	end

	if not panel.l.autoExpand and panel.f.w >= panel.symbol.stageWidth and panel.f.h >= panel.symbol.stageHeight then
		return
	end

	-- ltrb
	local addSize = selection and 0 or 32
	local push = bounds
		or (
			selection
				and {
					panel.selection.position.x - panel.selection.scale.x * (panel.selection.bbox.w / 2),
					panel.selection.position.y - panel.selection.scale.y * (panel.selection.bbox.h / 2),
					panel.selection.position.x + panel.selection.scale.x * (panel.selection.bbox.w / 2),
					panel.selection.position.y + panel.selection.scale.y * (panel.selection.bbox.h / 2),
				}
			or {
				panel.brushCoords.x - addSize,
				panel.brushCoords.y - addSize,
				panel.brushCoords.x + addSize,
				panel.brushCoords.y + addSize,
			}
		)
	local frame = { 0, 0, panel.f.w, panel.f.h }
	local pushed = {}
	local add = { x = 0, y = 0, w = 0, h = 0 }
	if selection then
		panel.selection.expanded = { x = 0, y = 0, w = 0, h = 0 }
	end

	if panel.f.w < gfx.maxTextureSize() and panel.f.h < gfx.maxTextureSize() then
		-- if bounds then
		-- print(inspect(bounds))
		-- end
		-- print(panel.brushCoords.x,panel.brushCoords.y)
		-- print("push",inspect(push,{process=mf}))

		pushed = {
			math.min(frame[1], push[1]),
			math.min(frame[2], push[2]),
			math.max(frame[3], push[3]),
			math.max(frame[4], push[4]),
		}

		add = {
			x = frame[1] - pushed[1],
			y = frame[2] - pushed[2],
			w = pushed[3] - frame[3],
			h = pushed[4] - frame[4],
		}

		local padding = push[5] or 64

		add = {
			x = mf((add.x > 0 and add.x + padding or 0) / 32) * 32,
			y = mf((add.y > 0 and add.y + padding or 0) / 32) * 32,
			w = mf((add.w > 0 and add.w + padding or 0) / 32) * 32,
			h = mf((add.h > 0 and add.h + padding or 0) / 32) * 32,
		}

		if add.x ~= 0 or add.y ~= 0 or add.w ~= 0 or add.h ~= 0 then
			print("add", add.x, add.y, add.w, add.h)

			local new_w = panel.f.w + mf(math.abs(add.x) + add.w)
			local new_h = panel.f.h + mf(math.abs(add.y) + add.h)

			if new_w > gfx.maxTextureSize() or new_h > gfx.maxTextureSize() then
				return
			end
			print("neww/h", new_w, new_h)
			-- actually expand --------------------------
			panel.f.w = new_w
			panel.f.h = new_h

			panel.f.ox = panel.f.ox - ((add.x - add.w) / 2) * panel.l.resolution
			panel.f.oy = panel.f.oy - ((add.y - add.h) / 2) * panel.l.resolution

			print("panel.f.ox", panel.f.ox, panel.f.oy)
			-- editor_shiftLines(add.x,add.y)

			-- local new = newCanvas(new_w,new_h)
			-- gfx.push()
			-- gfx.setCanvas(new)
			-- gfx.setBlendMode("replace")
			-- gfx.clear(0,0,0,0)
			-- gfx.setShader()
			-- gfx.setColor(1,1,1,1)
			-- print("addy",add.y)
			-- gfx.draw(panel.f.canvas,add.x,add.y<0 and add.y or add.h)
			-- gfx.pop()
			-- panel.f.canvas=new

			local new = gfx.copyCanvas(panel.f.canvas, {
				release = true,
				x = add.x,
				y = add.y < 0 and add.y or add.h,
				nw = panel.f.w,
				nh = panel.f.h,
				filter = panel.filter,
			})

			panel.f.canvas:release()

			panel.f.canvas = new

			if not selection then
				panel.brushCanvas = gfx.copyCanvas(panel.brushCanvas, {
					release = true,
					x = add.x,
					y = add.y < 0 and add.y or add.h,
					nw = panel.f.w,
					nh = panel.f.h,
					filter = panel.filter,
				})
			end

			panel.updateMesh = true

			panel.brushCoords.x = panel.brushCoords.x + add.x
			panel.brushCoords.y = panel.brushCoords.y + add.y

			if panel.brushPrev then
				panel.brushPrev.x = panel.brushPrev.x + add.x
				panel.brushPrevPrev.x = panel.brushPrevPrev.x + add.x
				panel.diff.x = panel.diff.x + add.x

				panel.brushPrev.y = panel.brushPrev.y + add.y
				panel.brushPrevPrev.y = panel.brushPrevPrev.y + add.y
				panel.diff.y = panel.diff.y + add.y
			end

			if selection then
				panel.selection.expanded = add
			end
			----------------------
		end
	else
		print("Canvas Too Big!", panel.f.w, panel.f.h)
	end
end

function editor_shiftLines(x, y)
	if not (animatePanel.f.strokes and type(animatePanel.f.strokes) == "table") then
		return
	end
	for s, stroke in ipairs(animatePanel.f.strokes) do
		local line = stroke.line
		for i = 1, line[0][0], 1 do
			line[i][1], line[i][2] =
				line[i][1] + x * animatePanel.l.resolution, line[i][2] + y * animatePanel.l.resolution
		end
	end
	editor_refreshStrokes()
end

function editor_insertStrokePoint(panel, stroke, point, stable)
	local line = stroke.line
	local l = line[0][0]

	-- if math.dist(point.x,point.y,line[l][1],line[l][2])<1 then
	--  return
	-- end

	local ax, ay, bx, by = point.x, point.y, 0, 0

	if l > 0 then
		ax, ay = (line[l][1] + point.x) / 2, (line[l][2] + point.y) / 2
	end

	-- print(ax,ay,line[l][1],point.x)
	-- print(bx,   by,   cx,   cy)
	-- print("panel.pressureSize2",panel.pressureSize2)
	if panel.pressureSize2 == 0 then
		return
	end
	appendStrokeLinePoint(
		stroke,
		point.x,
		point.y,
		ax,
		ay,
		point.x,
		point.y, -- temp
		panel.pressureSize2,
		panel.pressureOpacity2
	)

	l = line[0][0]

	if line[0][0] > 2 then
		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

		if panel.debugView then
			-- table.insert(panel.f.strokes,{size=0.1, ends=1, color={1,0,0,1}, group="note",
			--  line={{ax,ay},{bx,by},{ax,ay}}
			-- })
		end
	end
end

function editor_bezierStroke(c1, c2, c3, pen)
	local x, y = pen and "ax" or "x", pen and "ay" or "y"

	c2[x], c2[y] = c2[x] or c3[x], c2[y] or c3[y]
	c1[x], c1[y] = c1[x] or c2[x], c1[y] or c2[y]

	local f = 0.25
	local midPoint = (vec2(c2[x], c2[y]) + vec2(c3[x], c3[y])) / 2
	local s = 100
	local a = vec2(c2[x] - c1[x], c2[y] - c1[y])
	local b = vec2(c3[x] - c2[x], c3[y] - c2[y])
	local an, bn = vec2.normalize(a), vec2.normalize(b)
	local dot = an.x * bn.x + an.y * bn.y
	-- print("dot",dot,mouse.moved,an,bn)
	local perp = vec2.perpendicular(b)
	local l1 = vec2(c2[x], c2[y])
	local l1a, l1b = l1, l1 + a * s
	local dot2 = vec2.dot(a, perp)
	local l2a, l2b = midPoint, midPoint + perp * s * (dot2 > 0 and 1 or -1)

	local projected = vec2(midPoint.x, midPoint.y)

	if dot > 0 and b:len() > 10 and a:len() > 10 then
		local ix, iy = intersect(l1a.x, l1a.y, l1b.x, l1b.y, l2a.x, l2a.y, l2b.x, l2b.y)
		-- print("hi",ix,iy)
		if ix then
			local add = (vec2(ix, iy) - midPoint)
			projected = midPoint + add --*(a:len()/b:len())
			-- print(dot)
			if vec2.dist(midPoint, projected) > b:len() then
				projected = midPoint
				f = 0.5
				-- print("ABORT1")
			end
		else
			-- print("good")
		end
	else
		-- print("ABORT2",dot,a:dot(b),a,b,b:len(),a:len())
	end

	return mth.newBezierCurve(
		c2[x],
		c2[y],
		lerp(c2[x], projected.x, f),
		lerp(c2[y], projected.y, f),
		lerp(c3[x], projected.x, f),
		lerp(c3[y], projected.y, f),
		c3[x],
		c3[y]
	)

	-- local projectedPoint = {
	--  x = c2[x] + (c2[x] - c1[x]) / 2,
	--  y = c2[y] + (c2[y] - c1[y]) / 2
	-- }
	-- projectedPoint.x = (projectedPoint.x + (c3[x] + c2[x]) / 2) / 2
	-- projectedPoint.y = (projectedPoint.y + (c3[y] + c2[y]) / 2) / 2

	-- return mth.newBezierCurve(c2[x], c2[y], projectedPoint.x, projectedPoint.y,projectedPoint.x, projectedPoint.y, c3[x], c3[y])
end

function editor_bezierControlPoint(ax, ay, bx, by, cx, cy)
	local lenl, lenr = math.dist(ax, ay, bx, by), math.dist(bx, by, cx, cy)
	local dl = lenl / (lenl + lenr)
	local lx, ly = (ax + bx) / 2, (ay + by) / 2
	local rx, ry = (bx + cx) / 2, (by + cy) / 2
	local mx, my = lerp(lx, rx, dl), lerp(ly, ry, dl)
	local k = 0.6
	return bx + (lx - mx) * k, by + (ly - my) * k, bx + (rx - mx) * k, by + (ry - my) * k
end

function editor_curveStroke(panel)

	if mouse_release() then
		return
	end
	panel.mouseTip = panel.mouseTip or lume.clone(panel.brushCoords)

	panel.tipDist = panel.tipDist
		+ math.dist(panel.brushCoords.x, panel.brushCoords.y, panel.brushPrev.x, panel.brushPrev.y)
	panel.tipDist2 = panel.tipDist2
		+ math.dist(panel.brushCoords.x, panel.brushCoords.y, panel.brushPrev.x, panel.brushPrev.y)
	local brushLim = 1 --((panel.brush.spacing + 0.01) * (editor_getBrushSize(panel)) * 4) or (panel.drawn and (panel.drawn < 5))
	local penLim = panel.stroke and (panel.stroke.line[0][0] < 2 and -1 or 5)
	if level.isAnim then
		penLim = (panel.stroke and panel.stroke.line[0][0] < 2) and -1
			or utils.map(ctx.views[1].camera.soom, 0.8, 1.2, 1, 6)
	end

	local lim = panel.l.format == "vector" and penLim or brushLim

	local point = vec2()
	local curve =
		editor_bezierStroke(panel.brushPrevPrev, panel.brushPrev, panel.brushCoords, panel.l.format == "vector")
	local stable = (panel.tipDist < 20 and 0.02 or 0) + panel.pen.stabilizer / 3

	-- trace("td",panel.tipDist2,panel.drawTime)
	if panel.tipDist2 > 1 then
		if panel.l.format == "vector" and panel.previewStroke then
			point.x, point.y = panel.brushCoords.ax, panel.brushCoords.ay
			if panel.brush.paint then
				panel.pressureOpacity2 = panel.color.a
			end
			editor_insertStrokePoint(panel, panel.previewStroke, point, stable)
			-- if panel.stroke and #panel.stroke==0 then
			--  panel.stroke.line[1][1]=panel.previewStroke.line[1][1]
			--  panel.stroke.line[1][2]=panel.previewStroke.line[1][2]
			-- end
			-- if #panel.previewStroke.widths>10 then
			-- editor.deleteStrokePoint(panel.previewStroke,1)
			-- end
		end
		panel.tipDist2 = 0
	end

	local smoothing = panel.pen.smoothing
	local mul = 1 + smoothing * 3

	if panel.brush.paint then
		-- if editor_getBrushSize(panel)>5 then
		--  smoothing = (1+smoothing)/2
		--  mul = 1+smoothing
		-- else
		smoothing = (1 + smoothing) / 3
		mul = 1 + smoothing / 3
		-- end
	end

	-- trace("penLim",lim,panel.tipDist)
	local dot = 1
	local dot2 = dot
	local shouldDraw

	if panel.tool ~= "select" then
		if prevCoords and not (prevCoords.ax == panel.brushCoords.ax and panel.brushCoords.ay == prevCoords.ay) then
			panel.drawTime = panel.drawTime + 1
		end
	end

	local line
	if panel.stroke and panel.l.format == "vector" then
		line = panel.stroke.line
		if panel.drawTime == 1 then
			-- shouldDraw=true
		end

		if line[0][0] > 1 then
			local px, py = line[line[0][0]][1], line[line[0][0]][2]
			local ppx, ppy = line[line[0][0] - 1][1], line[line[0][0] - 1][2]
			local v1, v2 = vec2(panel.brushCoords.ax - px, panel.brushCoords.ay - py), vec2(px - ppx, py - ppy)
			dot2 = vec2.dot(v1:normalize(), v2:normalize())
			dot = (dot + 1 + smoothing) ^ mul
		end
	end

	-- print(panel.tipDist , dot*lim,panel.tipDist > dot*lim, dot<0 )
	if panel.tipDist > dot * lim or dot < 0 then
		shouldDraw = true
	end

	-- trace("tipdist",panel.tipDist , dot*lim,shouldDraw)

	if panel.stroke and panel.stroke.line[0][0] == 0 then
		point.x, point.y = panel.brushCoords.ax, panel.brushCoords.ay
		editor_insertStrokePoint(panel, panel.stroke, point, stable)
		print("HEYYYYYYYYYYYYYYYYYYYYYY")
	end

	if shouldDraw then
		panel.mouseTip = lume.clone(panel.brushCoords)

		if not panel.brushMesh then
			local l, r = -0.5, 0.5
			panel.brushMesh = gfx.newMesh(zformat, {
				{ l, l, 0, 0, 0, 0, 1, 1, 1, 1 },
				{ r, l, 0, 0, 1, 0, 1, 1, 1, 1 },
				{ r, r, 0, 0, 1, 1, 1, 1, 1, 1 },
				{ l, l, 0, 0, 0, 0, 1, 1, 1, 1 },
				{ r, r, 0, 0, 1, 1, 1, 1, 1, 1 },
				{ l, r, 0, 0, 0, 1, 1, 1, 1, 1 },
			}, "triangles")
		end

		if panel.l.format == "raster" then
			-- local brushTexture = load_imageTex(brushes[panel.brush.shape])
			-- if brushTexture and not panel.brushMapped then
			--   panel.brushMapped=true
			--   local brushData = brushTexture:newImageData()
			--   --brushData:encode("png","prebrush.png")
			--   brushData:mapPixel(function(x,y,r,g,b,a)
			--     return 1,1,1,a
			--   end)
			--   --brushData:encode("png","postbrush.png")
			--   brushTexture:replacePixels(brushData)
			--   brushData=nil
			-- end
			-- gfx.setFilter(brushTexture,panel.brush.antialias and "linear" or "nearest")
			-- gfx.setTexture(panel.brushMesh,brushTexture)
		end

		if panel.tool == "select" then
			if panel.selection.shape then
				table.insert(
					panel.selection.shape,
					panel.l.format == "vector" and panel.brushCoords.x or mf(panel.brushCoords.x)
				)
				table.insert(
					panel.selection.shape,
					panel.l.format == "vector" and panel.brushCoords.y or mf(panel.brushCoords.y)
				)
			end
		elseif panel.l.format == "vector" and panel.stroke then
			-- if panel.brush.paint then
			--  if dot2<0 then
			--    print("INSERT////////////////////")
			--    point:set(vec2(panel.brushPrev.ax,panel.brushPrev.ay)+vec2(panel.brushCoords.ax-panel.brushPrev.ax,panel.brushCoords.ay-panel.brushPrev.ay):perpendicular():normalize())
			--    editor_insertStrokePoint(panel,panel.stroke,point,stable)
			--  end
			-- end
			point.x, point.y = panel.brushCoords.ax, panel.brushCoords.ay
			local dist = panel.brush.paint and math.dist(point.x, point.y, line[line[0][0]][1], line[line[0][0]][2])
				or 3
			-- print(point.x, point.y,dist,dist<2)
			if dist > 1 then
				if panel.previewStroke.line[0][0] == 0 then
					editor_insertStrokePoint(panel, panel.previewStroke, point, stable)
				else
					editor_insertStrokePoint(panel, panel.stroke, point, stable)
					-- print("insert point")
				end
			end
			if smoothing > 0.01 then
				-- editor.smoothStroke(panel,panel.stroke,smoothing)
			end
		elseif panel.l.format == "raster" then
			local iterations = panel.tool == "select" and 2
				or mu((panel.tipDist / (editor_getBrushSize(panel) - 1))) * 3

			if math.abs((panel.brush.mouseAng + 10) - (panel.brushPrev.angle + 10)) > 0.4 then
				-- iterations=iterations*4
			end

			local spacing = panel.brush.spacing
			iterations = iterations * (1 + 4 * panel.brush.flatness)
			if spacing > 0 then
				iterations = iterations
					* (spacing < 0.5 and (math.max(0, (1 - spacing * 1.5))) or (math.max(0, (1 - spacing)) ^ 2))
			else
				iterations = iterations * 2
			end
			iterations = mc(1, iterations, 300)
			iterations = mu(iterations)
			-- print(iterations)

			gfx.push("all")
			gfx.setShader(shaders.whiteShader)
			shaders.whiteShader:send("antialias", panel.brush.antialias and 1 or 0)

			gfx.setBlendMode("lighten")
			gfx.setMeshCullMode("none")

			for i = 0, iterations - 1, 1 do
				local tipAngle = lerpAng(panel.brushPrev.angle, panel.brush.mouseAng, i / iterations)

				local t = i / iterations
				if t < 0.5 then
				-- t=lerp(t,(math.sin(t*pi))/2,1)
				else
					-- t=lerp(t,(1-math.sin(t*pi)/2),0.5)
				end
				point.x, point.y = curve:evaluate(t)

				local lerpSize = lerp(panel.pressureSize1, panel.pressureSize2, i / iterations)
					* (
						lerp(
							editor_getBrushSize(panel) or panel.brushPrev.size,
							editor_getBrushSize(panel),
							i / iterations
						) / 64
					)

				if panel.brush.scatter > 0.05 then
					local scatterAngle = math.random() * 2 * pi
					local scatterDist = math.random() * panel.brush.scatter * 64
					point.x = point.x + math.sin(scatterAngle) * scatterDist
					point.y = point.y + math.cos(scatterAngle) * scatterDist
				end

				-- brushTexture:setFilter("nearest","nearest")

				local alpha = lerp(panel.pressureOpacity1, panel.pressureOpacity2, i / iterations)

				-- print("uhoh",alpha,panel.pressureOpacity1,panel.pressureOpacity2)

				panel.brushAlpha = alpha
				-- panel.brushAlpha = mc(0,panel.strokeTime>4 and easeTo(panel.brushAlpha,alpha,2) or alpha,1)

				-- trace("panel.brushAlpha",panel.pressureOpacity1,panel.pressureOpacity2,panel.brushAlpha)

				gfx.setColor(1, 1, 1, panel.brushAlpha * panel.brush.opacity)

				local ang = panel.brush.angle + (panel.brush.followDirection and tipAngle or 0)
				local size = 32 * lerpSize
				local ox, oy = math.sin(ang) * size, math.cos(ang) * size

				-- gfx.setColor(1,1,1,t)
				gfx.push()
				gfx.translate(point.x, panel.f.h - point.y)
				gfx.rotate(-ang)
				gfx.scale(1 - panel.brush.flatness * 0.8, 1)
				gfx.scale(lerpSize * 64, lerpSize * 64)
				gfx.draw(panel.brushMesh)
				gfx.pop()
			end
			gfx.pop()
		end
		panel.brushCoords.size = editor_getBrushSize(panel)
		panel.tipDist = 0
	end
end

function editor_registerSelection(panel)
	panel.selection.format = panel.l.format
	panel.justSelected = true
	panel.selection.selected = true
	panel.selection.angle = 0
	panel.selection.scale = { x = 1, y = 1 }
	panel.selection.position = {
		x = panel.selection.bbox[1] + panel.selection.bbox.w / 2,
		y = panel.selection.bbox[2] + panel.selection.bbox.h / 2,
	}
	panel.selection.diff =
		{ x = panel.selection.position.x - panel.f.w / 2, y = panel.selection.position.y - panel.f.h / 2 }
	panel.selection.offset = lume.clone(panel.selection.position)
	panel.selection.cut = false
	-- panel.selection.f = {x=panel.f.ox - panel.f.w , y=panel.f.oy - panel.f.h}
end

function editor_clearFrameDebugStrokes(panel, force)
	panel.debugStrokes={}
end

function editor_selectStrokes(panel, linesOnly)
	if not (panel.selection.shape and not shiftDown and #panel.selection.shape > 5) then
		panel.selection = {}
		return
	end

	panel.selection.bbox = bboxLine(panel.selection.shape)

	if not (panel.selection.bbox.w > 1 and panel.selection.bbox.h > 1) then
		panel.selection = {}
		return
	end
	print("select box", panel.selection.bbox.w, panel.selection.bbox.h)

	local shape = lume.clone(panel.selection.shape)
	local couldSelect = false
	local bbox
	local tmpBez = mth.newBezierCurve(0, 0, 0, 0, 0, 0, 0, 0)
	local region = {}
	for l = 1, #shape - 1, 2 do
		if not (l > 1 and shape[l] == shape[l - 2] and shape[l + 1] == shape[l - 1]) then
			table.insert(region, { shape[l], shape[l + 1] })
		end
	end
	table.insert(region, { shape[1], shape[2] })
	local selectionStroke = newStroke({
		group = "line",
		line = region,
	})
	editor_strokeBounds(selectionStroke)

	if panel.l.format == "vector" then
		_start_timer("select vectors")

		editor_clearFrameDebugStrokes(panel)

		if panel.debugView then
			panel.debugSelection = lume.clone(panel.selection)
			fill_addTestLines({ region })
		end

		panel.selection.additions = {}
		panel.selection.removals = {}
		panel.selection.frames = {}

		local removeHash = {}
		local dist = math.dist
		local strokesSliced = {}
		local min, max = math.min, math.max
		local point = {}
		local debugSelect = panel.debugView and panel.debugSelect

		_time()
		panel.debugSelectionOffset = 0
		for l, layer in ipairs(panel.layers) do
			if editor_filterLayer(panel, layer, panel.selectLayer) then
				print("FILTERED LAYER")
				local frame = layer.frames[layer.currentFrame]
				frame.layer = l
				frame.index = layer.currentFrame
				table.insert(panel.selection.frames, frame)
				for s, stroke in ipairs(frame.strokes) do
					if
						not stroke.debug
						and editor_filterStrokeGroup(panel, stroke, panel.selectGroup)
						and bboxCollide1(panel.selection.bbox, stroke.bbox or editor_strokeBounds(stroke))
					then
						if stroke.group == "fill" and not linesOnly then
							selectionStroke.index = 0
							stroke.index = 1
							panel.debugSelectionOffset = panel.debugSelectionOffset + 300
							local segs, bbbox, ints =
								fill_splitSegments({ selectionStroke, stroke }, false, true, false, true, true, true)
							if segs then
								for s = segs[0][0], 1, -1 do
									local seg = segs[s]
									point[1], point[2] = (seg[1] + seg[3]) / 2, (seg[2] + seg[4]) / 2
									if seg[10] == 0 and not fill_pointInFill(panel, stroke, point) then
										fill_copyFillSeg(segs[segs[0][0]], seg)
										segs[0][0] = segs[0][0] - 1
									else
										if panel.debugView then
											fill_addTestLines({
												{
													ox = -bbbox.w * 3,
													oy = panel.debugSelectionOffset,
													seg[1],
													seg[2],
													seg[3],
													seg[4],
												},
											})
										end
									end
								end
								local filled = fillv_fillVector(panel, segs, bbbox, true, false, stroke)

								fill_addTestLines(filled.testpolys)
								if #filled.shapes > 1 then
									table.insert(
										panel.selection.removals,
										{ frame = frame, stroke = stroke, stroke_index = s }
									)
									for f, fill in ipairs(filled.shapes) do
										local new = newStroke({
											color = lume.clone(stroke.color),
											group = "fill",
											line = fill.line,
											point = fill.point,
										})
										if panel.debugView then
											fill_addTestLines({
												{
													color = { r = 1, g = 0, b = 0 },
													ox = bbbox.w * 2,
													oy = panel.debugSelectionOffset,
													fill.point[1] - 2,
													fill.point[2] - 2,
													fill.point[1] + 2,
													fill.point[2] + 2,
												},
											})
											fill_addTestLines({
												{
													color = { r = 1, g = 0, b = 0 },
													ox = bbbox.w * 2,
													oy = panel.debugSelectionOffset,
													fill.point[1] + 2,
													fill.point[2] - 2,
													fill.point[1] - 2,
													fill.point[2] + 2,
												},
											})
										end
										table.insert(panel.selection.additions, { frame = frame, stroke = new })
									end
								end
							end
						elseif stroke.group == "line" then
							local slicedStroke = false
							local line = stroke.line
							for i = 1, line[0][0] - 1, 1 do
								local slicedSeg = false
								local j = i + 1
								local w = line[i][7] * (stroke.size * layer.lineWidth) / 2
								local ax, ay, dx, dy = line[i][1], line[i][2], line[j][1], line[j][2]
								local bx, by, cx, cy = line[i][5], line[i][6], line[j][3], line[j][4]

								local steps = 2
									+ mu(
										0.1
											* (
												10
												+ dist(ax, ay, bx, by)
												+ dist(bx, by, cx, cy)
												+ dist(cx, cy, dx, dy)
												+ dist(ax, ay, dx, dy)
											)
									)

								setControlPoints(tmpBez, ax, ay, bx, by, cx, cy, dx, dy)

								local x1, y1
								for step = 0, steps, 1 do
									local t0 = (step - 1) / steps
									local t = step / steps
									local x2, y2 = tmpBez:evaluate(t)
									if PointWithinShape(panel.selection.shape, x2, y2) then
										bbox = bbox or { x2, y2, x2, y2 }
										bbox[1], bbox[2], bbox[3], bbox[4] =
											min(bbox[1], x2 - w),
											min(bbox[2], y2 - w),
											max(bbox[3], x2 + w),
											max(bbox[4], y2 + w)
									end

									if x1 and not panel.selection.all then
										local sline = selectionStroke.line
										for r = 1, sline[0][0], 1 do
											local x, y = intersect(
												sline[r][1],
												sline[r][2],
												r == sline[0][0] and sline[1][1] or sline[r + 1][1],
												r == sline[0][0] and sline[1][2] or sline[r + 1][2],
												x1,
												y1,
												x2,
												y2
											)
											if x then
												if debugSelect then
													fill_addTestLines({ { x, y, x + 1, y + 1, x - 1, y + 1, x, y } })
												end
												if not slicedStroke then
													strokesSliced[stroke] =
														{ frame = frame, stroke = stroke, index = s }
													slicedStroke = strokesSliced[stroke]
												end
												if not slicedSeg then
													table.insert(slicedStroke, { i = i })
													slicedSeg = slicedStroke[#slicedStroke]
												end
												local t2 = lerp(t0, t, dist(x1, y1, x, y) / dist(x1, y1, x2, y2))
												local w = lerp(line[i][7], line[j][7], t)
												local a = lerp(line[i][8], line[j][8], t)
												table.insert(slicedSeg, { x, y, w, a, t2 })

												bbox = bbox or { x, y, x, y }
												bbox[1], bbox[2], bbox[3], bbox[4] =
													min(bbox[1], x - w),
													min(bbox[2], y - w),
													max(bbox[3], x + w),
													max(bbox[4], y + w)
											end
										end
									end

									x1, y1 = x2, y2
								end
							end
						end
					end
				end
			end
		end
		_time("select strokes 1")
		---------------------------------------------------
		local function sliceSort(a, b)
			return a[5] < b[5]
		end
		local old = ffi.new("float[24]")
		local new = ffi.new("float[24]")
		-- print("\n------------------------------------------------------")
		-- print("strokesSliced",lume.count(strokesSliced))
		for k, v in pairs(strokesSliced) do
			-- print(k,v.stroke,v.frame)
		end
		for s1, strokeSlices in pairs(strokesSliced) do
			local frame = strokeSlices.frame
			-- print("frame",strokeSlices.frame,strokeSlices.frame.id)
			local stroke = strokeSlices.stroke
			local sindex = strokeSlices.index
			local line = stroke.line
			local start = 1
			local sx, sy, ax, ay, bx, by
			local w, a = 1, 1
			-- print("cut index:")
			for s2, segSlices in ipairs(strokeSlices) do
				local i = segSlices.i
				-- print(" ",i)
				old[1] = line[i][1]
				old[2] = line[i][2]
				old[3] = line[i + 1][1]
				old[4] = line[i + 1][2]
				old[5] = line[i][5]
				old[6] = line[i][6]
				old[7] = line[i + 1][3]
				old[8] = line[i + 1][4]

				table.sort(segSlices, sliceSort)
				local t1 = 0

				for s3, slice in ipairs(segSlices) do
					local left = copyStroke(stroke, start, i)
					if left then
						if sx then
							prependStrokeLinePoint(left, sx, sy, ax, ay, bx, by, w, a)
						end
						print("post prepend")
						sx, sy, w, a = slice[1], slice[2], slice[3], slice[4]

						local t2 = slice[5]
						fill_sliceCurve(old, new, t1, t2)
						ax, ay = new[7], new[8]
						t1 = slice[5]
						t2 = s3 < #segSlices and segSlices[s3 + 1][5] or 1
						fill_sliceCurve(old, new, t1, t2)
						bx, by = new[5], new[6]
						print("sliced twice")

						appendStrokeLinePoint(left, sx, sy, ax, ay, bx, by, w, a)
						print("appendeded")
						table.insert(panel.selection.additions, { frame = frame, stroke = left })
						editor_strokeBounds(left)
					end
					start = i + 1
				end
			end

			local right = copyStroke(stroke, start)
			if right then
				prependStrokeLinePoint(right, sx, sy, ax, ay, bx, by, w, a)
				editor_strokeBounds(right)
				table.insert(panel.selection.additions, { frame = frame, stroke = right })
			end

			table.insert(panel.selection.removals, { frame = frame, stroke = stroke, stroke_index = sindex })
		end

		_time("select strokes 2")
		for r, removal in ipairs(panel.selection.removals) do
			removeHash[removal.stroke] = removal.stroke_index
		end

		panel.selection.format = "vector"
		panel.selection.strokes = {}
		panel.selection.strokeFrames = {}

		local filterFunction = function(a, b, c)
			return true
		end -- editor_filterStrokeGroup
		for f, frame in ipairs(panel.selection.frames) do
			-- print("check frame ",f)
			local l = frame.layer
			local additions = {}
			for a, addition in ipairs(panel.selection.additions) do
				if frame == addition.frame then
					table.insert(additions, addition)
				end
			end
			-- print("#additions",#additions)
			local fstrokes = #frame.strokes
			for s = fstrokes + #additions, 1, -1 do
				local stroke = s <= fstrokes and frame.strokes[s] or additions[s - fstrokes].stroke
				-- print(s,s-fstrokes,fstrokes,#additions)
				if stroke and stroke.line and not stroke.debug and filterFunction(panel, stroke, panel.selectGroup) then
					local skip = (s <= fstrokes and removeHash[stroke]) and true or false
					local line = stroke.group == "fill" and stroke.line[1] or stroke.line
					local n, m = line[0][0], line[0][0] - 1
					if stroke.group == "fill" and linesOnly then
						skip = true
					end
					-- print("s,skip",
					--   s<=fstrokes and "frame.strokes" or "additions",
					--   s<=fstrokes and s or s-fstrokes,
					--   skip
					-- )
					if not skip then
						local inRegion = false
						local tester = panel.tool == "move" and pointInBBox or PointWithinShape
						local testee = panel.tool == "move" and panel.selection.bbox or selectionStroke.line

						if stroke.point then
							local pt = stroke.point
							inRegion = tester(testee, pt[1], pt[2], true)
							if panel.debugView then
								local color = inRegion and { r = 0, g = 0, b = 1 } or { r = 1, g = 0, b = 0 }
								fill_addTestLines({ { color = color, pt[1] - 2, pt[2] - 2, pt[1] + 2, pt[2] + 2 } })
								fill_addTestLines({ { color = color, pt[1] + 2, pt[2] - 2, pt[1] - 2, pt[2] + 2 } })
							end
						else
							inRegion = tester(
								testee,
								(line[1][1] + line[2][1]) / 2,
								(line[1][2] + line[2][2]) / 2,
								true
							) and tester(
								testee,
								(line[m][1] + line[n][1]) / 2,
								(line[m][2] + line[n][2]) / 2,
								true
							)
						end

						if inRegion and not lume.find(panel.selection.strokes, stroke) then
							couldSelect = true
							table.insert(panel.selection.strokes, stroke)
							table.insert(panel.selection.strokeFrames, { frame.index, l })
							-- if selected, remove from additions
							for a, addition in ipairs(panel.selection.additions) do
								if addition.stroke == stroke then
									table.remove(panel.selection.additions, a)
									break
								end
							end
						end
					end
				end
			end
		end

		bbox = nil
		_time("select strokes 3")
		for s, stroke in ipairs(panel.selection.strokes) do
			editor_strokeBounds(stroke)
			bbox = bbox or { stroke.bbox[1], stroke.bbox[2], stroke.bbox[3], stroke.bbox[4] }
			-- editor_addTestLines({{color={r=1/s,g=0,b=0},stroke.bbox[1],stroke.bbox[2],stroke.bbox[3],stroke.bbox[4]}})
			bbox[1], bbox[2], bbox[3], bbox[4] =
				min(bbox[1], stroke.bbox[1]),
				min(bbox[2], stroke.bbox[2]),
				max(bbox[3], stroke.bbox[3]),
				max(bbox[4], stroke.bbox[4])
		end

		editor_refreshStrokes()
		panel.sudoForceRestroke = true
		_disable_timers()
		vector_genVectorSelectionMesh(panel)
		_enable_timers()
		_time("select strokes 4")

		print("panel.selection.strokes:", #panel.selection.strokes)
		-- panel.selection.strokes=nil
		-- panel.selection.strokeFrames=nil
		---------------------------------------------------
		_end_timer("select vectors")
	else
		bbox = panel.selection.bbox
		couldSelect = true
	end

	panel.selection.symbols = {}
	for l, layer in ipairs(panel.layers) do
		if editor_filterLayer(panel, layer, panel.selectLayer) then
			local frame = layer.frames[layer.currentFrame]
			for s, symbol in ipairs(layer.frames[layer.currentFrame].symbols) do
				if PointWithinShape(selectionStroke.line, symbol.position.x, symbol.position.y, true) then
					couldSelect = true
					table.insert(panel.selection.symbols, {
						frame = frame.id,
						uid = symbol.uid,
						position = vec3.clone(symbol.position),
					})
					bbox = bbox or { symbol.position.x, symbol.position.y, symbol.position.x, symbol.position.y }
					-- symbolBounds(symbol)
					-- bbox[1], bbox[2], bbox[3], bbox[4]=
					--   min(bbox[1],symbol.bbox[1]), min(bbox[2],symbol.bbox[2]),
					--   max(bbox[3],symbol.bbox[3]), max(bbox[4],symbol.bbox[4])
				end
			end
		end
	end

	if not bbox then
		return
	end

	bbox.w = bbox[3] - bbox[1]
	bbox.h = bbox[4] - bbox[2]

	if (panel.cropSelect or panel.selection.all) and #panel.selection.symbols == 0 then
		panel.selection.bbox = bbox
	end

	if not couldSelect then
		return
	end

	print("select region")
	panel.selection.dontPaste = true

	panel.pushUndoState("Select Region")
	gfx.setColor(1, 1, 1, 1)
	--editor.splitPoly(panel.selection.shape)
	panel.selection.verts = {}
	for i = 1, #panel.selection.shape * 3, 2 do
		table.insert(panel.selection.verts, {
			panel.selection.shape[wrap2(i, 1, #panel.selection.shape)],
			panel.selection.shape[wrap2(i + 1, 1, #panel.selection.shape)],
			1,
			0,
		})
		table.insert(panel.selection.verts, {
			panel.selection.shape[wrap2(i + 2, 1, #panel.selection.shape)],
			panel.selection.shape[wrap2(i + 3, 1, #panel.selection.shape)],
			1,
			0,
		})
		table.insert(panel.selection.verts, {
			panel.selection.shape[wrap2(i, 1, #panel.selection.shape)],
			panel.selection.shape[wrap2(i + 1, 1, #panel.selection.shape)],
			1,
			0,
		})
	end
	panel.selection.mesh = gfx.newMesh(zformat, panel.selection.verts, "triangles")
	-- panel.selection.mesh:setVertices(panel.selection.verts)

	if panel.l.format == "raster" then
		local succ, triangles = pcall(mth.triangulate, panel.selection.shape)

		if not succ then
			print("selection failed!")
			editor_tempTip("selection failed!")
			panel.selection = {}
			return
		end

		local verts = {}
		for t = 1, #triangles, 1 do
			local tri = triangles[t]
			table.insert(verts, { tri[1], tri[2], 0, 0, tri[1] / panel.f.w, tri[2] / panel.f.h, 1, 1, 1, 1 })
			table.insert(verts, { tri[3], tri[4], 0, 0, tri[3] / panel.f.w, tri[4] / panel.f.h, 1, 1, 1, 1 })
			table.insert(verts, { tri[5], tri[6], 0, 0, tri[5] / panel.f.w, tri[6] / panel.f.h, 1, 1, 1, 1 })
		end
		panel.eraseMesh = gfx.newMesh(zformat, verts, "triangles")

		local l, t, w, h =
			panel.selection.bbox[1], panel.selection.bbox[2], panel.selection.bbox.w, panel.selection.bbox.h

		print("tri[1]", l, w, triangles[1][1], triangles[1][1] - l)
		print("tri[2]", t, h, triangles[1][2], triangles[1][2] - t - h / 2)

		local verts = {}
		for i = 1, #triangles, 1 do
			local tri = triangles[i]
			table.insert(
				verts,
				{ tri[1] - l - w / 2, -(tri[2] - t - h / 2), 0, 0, tri[1] / panel.f.w, tri[2] / panel.f.h, 1, 1, 1, 1 }
			)
			table.insert(
				verts,
				{ tri[3] - l - w / 2, -(tri[4] - t - h / 2), 0, 0, tri[3] / panel.f.w, tri[4] / panel.f.h, 1, 1, 1, 1 }
			)
			table.insert(
				verts,
				{ tri[5] - l - w / 2, -(tri[6] - t - h / 2), 0, 0, tri[5] / panel.f.w, tri[6] / panel.f.h, 1, 1, 1, 1 }
			)
		end

		panel.selection.shapeMesh = gfx.newMesh(zformat, verts, "triangles")

		gfx.setTexture(panel.selection.shapeMesh, panel.f.canvas)

		panel.selection.canvas = newCanvas(mf(panel.selection.bbox.w), mf(panel.selection.bbox.h), panel.l.format)
		-- panel.selection.canvas = newCanvas(panel.f.w,panel.f.h, panel.l.format)

		-- panel.selection.canvas:setFilter(panel.filter, panel.filter)
		panel.selection.canvas:setFilter("nearest", "nearest")

		gfx.push("all")
		gfx.setCanvas(panel.selection.canvas)
		gfx.clear(0, 0, 0, 0)
		gfx.setBlendMode("replace")
		gfx.setColor(1, 1, 1, 1)
		gfx.setMeshCullMode("none")
		gfx.setShader(shaders.bleedAlpha)
		shaders.bleedAlpha:send("image_size", { panel.f.w, panel.f.h })
		gfx.draw(panel.selection.shapeMesh, w / 2, h / 2 - 0.1)
		gfx.pop()

		-- keep selection mesh
		-- for v=1,#verts,1 do
		--  local vert = verts[v]
		--  vert[5],vert[6] = (vert[1]+w/2)/w,1-(vert[2]+h/2)/h
		-- end
		-- panel.selection.shapeMesh:setVertices(verts)

		local verts = {}
		-- for w for h  in tiles, (warping)
		local w, h = panel.selection.bbox.w, panel.selection.bbox.h
		table.insert(verts, { -w / 2, -h / 2, 0, 0, 0, 1, 1, 1, 1, 1 })
		table.insert(verts, { w / 2, -h / 2, 0, 0, 1, 1, 1, 1, 1, 1 })
		table.insert(verts, { w / 2, h / 2, 0, 0, 1, 0, 1, 1, 1, 1 })
		table.insert(verts, { -w / 2, -h / 2, 0, 0, 0, 1, 1, 1, 1, 1 })
		table.insert(verts, { w / 2, h / 2, 0, 0, 1, 0, 1, 1, 1, 1 })
		table.insert(verts, { -w / 2, h / 2, 0, 0, 0, 0, 1, 1, 1, 1 })
		panel.selection.shapeMesh = gfx.newMesh(zformat, verts, "triangles")

		gfx.setTexture(panel.selection.shapeMesh, panel.selection.canvas)

		-- panel.selection.canvas:renderTo(function()
		--  gfx.clear(0,0,0,0)
		--  gfx.setBlendMode ('replace')
		--  gfx.setColor (1, 1, 1, 1)
		--  gfx.setMeshCullMode("none")
		--  gfx.draw(panel.selection.shapeMesh,
		--    -panel.selection.bbox[1], panel.selection.bbox[2]+panel.selection.bbox.h,
		--    0,1,-1
		--  )
		-- end)

		-- panel.selection.canvas:newImageData():encode("png","pre3.png")
	end

	editor_registerSelection(panel)
end

function editor_brushGamma(c)
	-- local ltr,ltg,ltb = c.r<=0.0031308,c.g<=0.0031308,c.b<=0.0031308
	--  c.r = ltr and c.r * 12.92 or 1.055 * math.pow(c.r, 1.0 / 2.4) - 0.055;
	--  c.g = ltg and c.g * 12.92 or 1.055 * math.pow(c.g, 1.0 / 2.4) - 0.055;
	--  c.b = ltb and c.b * 12.92 or 1.055 * math.pow(c.b, 1.0 / 2.4) - 0.055;
	--  print("gamme",c.r,c.g,c.b,1)
	return c.r, c.g, c.b, c.a
end

function editor_finishStroke(panel)
	if panel.tool == "move" then
		if panel.selectingRect then
			print("rect select pls")
			editor_prepareToolMouse(panel)
			panel.selection.shape = {
				panel.selectingRect[1].ax,
				panel.selectingRect[1].ay,
				panel.brushCoords.ax,
				panel.selectingRect[1].ay,
				panel.brushCoords.ax,
				panel.brushCoords.ay,
				panel.selectingRect[1].ax,
				panel.brushCoords.ay,
			}
			editor_selectStrokes(panel)
		end
	elseif
		panel.l.format == "vector" and (panel.tool == "brush" or (panel.tool == "erase" and panel.eraseMode == "brush"))
	then
    print("finish1")
		if not panel.stroke then
			return
		end
    print("finish2")
		panel.f.dirty = true
		panel.drawTime = 0

		-- if panel.strokePreviewUnderlay then
		-- 	gfx.push("all")
		-- 	gfx.setCanvas(panel.strokePreviewUnderlay)
		-- 	gfx.clear(0, 0, 0, 0)
		-- 	gfx.pop()
		-- end

    print("finish3")
		editor_refreshStrokes()

		if panel.pen.smoothing > 0.01 and panel.pen.advancedSmoothing then
			-- editor.smoothStroke(panel,panel.stroke,panel.pen.smoothing,true)
		end

		local v = vec2(panel.brushCoords.ax, panel.brushCoords.ay)
		-- print("\npreview",panel.previewStroke.line[1][1],panel.previewStroke.line[1][2])
		-- print("preview",panel.previewStroke.line[2][1],panel.previewStroke.line[2][2])
		-- print(" stroke",panel.stroke.line[1][1],panel.stroke.line[1][2])
    print("finish3")
		editor_insertStrokePoint(panel, panel.stroke, v, 0)
		-- editor_insertStrokePoint(panel,panel.stroke,v,0)
		local line = panel.stroke.line
		local tail = panel.stroke.line[0][0]
		line[tail][3], line[tail][4] = line[tail][1], line[tail][2]
		line[tail][5], line[tail][6] = line[tail][1], line[tail][2]
    print("finish4")
		if panel.brush.paint or (panel.tool == "erase" and panel.eraseMode == "brush") then
			bool_lineToFill(panel, panel.stroke, (panel.tool == "erase" and panel.eraseMode == "brush"))
		else
			panel.strokePreview = nil
			return
		end
    print("finish5")
	elseif panel.l.format == "vector" then
		editor_refreshStrokes()

	elseif panel.l.format == "raster" and panel.tool == "brush" then
		panel.drawTime = 0
		panel.f.dirty = true
		local dpb = panel.brush
		local data
		if not panel.brushCanvas then
			return
		end

		editor_drawBrushTexture(panel, panel.brushCanvas)

		fill_copyFrameCanvasToTemp(panel)

		gfx.push("all")
		gfx.setCanvas(panel.f.canvas)
		gfx.setBlendMode("replace")
		gfx.setShader(shaders.alphaMultiply)
		shaders.alphaMultiply:send("dest", panel.tempCanvas)
		-- shaders.alphaMultiply:send("coords",{})
		gfx.setColor(editor_brushGamma(panel.color))
		gfx.draw(panel.brushCanvas)
		gfx.pop()

    -- todo fix canvas packing
		local rasterDiff = editor_packCanvas(panel.f.canvas, panel.tempCanvas)
		panel.pushUndoData({ rasterDiff = rasterDiff })

		timeline_refresh()

		if animatePanel.debugView and animatePanel.debugCompaction then
			rasterChange = rasterDiff
			panel.f.packedCanvas = editor_packCanvas(panel.f.canvas)
			unpackedCanvas = editor_unpackCanvas(panel.f.packedCanvas) or unpackedCanvas
			unpackedCanvas:newImageData():encode("png", "unpacked.png")
		end
		-- data = panel.f.canvas:newImageData()
		-- data:encode("png","panel_f_after.png")
		-- panel.brushCanvas:newImageData():encode("png","brush.png")
	end
end

function editor_packRasterFrame(panel, frame) end

function editor_unpackRasterFrame(panel, frame) end

function editor_packCanvas(sourceCanvas, previousCanvas)
	print("\n----------------------")

	-- is this even worth doing now??
	-- editor_clipFrame( panel.f )

	local limit = 256
	local sml, med, lrg, xxl = 8, 16, 32, 64

	local dims = { w = sourceCanvas:getWidth(), h = sourceCanvas:getHeight() }

	local tileSize = (dims.w < sml * limit and dims.h < sml * limit) and sml
		or (dims.w < med * limit and dims.h < med * limit) and med
		or (dims.w < lrg * limit and dims.h < lrg * limit) and lrg
		or (dims.w < xxl * limit and dims.h < xxl * limit) and xxl
		or 128

	if previousCanvas then
		tileSize = 32
	end

	local w, h = dims.w / tileSize - 1, dims.h / tileSize - 1

	print(string.format("input %ix%i px canvas", dims.w, dims.h))
	print(string.format("compacting %ix%i tile canvas", w, h))
	print(string.format("using tile size %i", tileSize))

	local unpackerMap = newCanvas(w + 1, h + 1) -- THIS IS FOR UNPACKING

	gfx.push("all")
	local shader = previousCanvas and shaders.conservativeCompare or shaders.conservativeDownscale
	gfx.setShader(shader)
	shader:send("size", tileSize)
	shader:send("texsize", { w + 1, h + 1 })
	if previousCanvas then
		shader:send("toCompare", previousCanvas)
	end
	gfx.setCanvas(unpackerMap)
	gfx.clear(0, 0, 0, 0)
	-- sourceCanvas:setFilter("nearest","nearest")
	gfx.setTexture(RectMesh, sourceCanvas)
	gfx.draw(RectMesh, 0, h + 1, 0, w + 1, -(h + 1))
	-- gfx.draw(sourceCanvas,0,0,0,1/tileSize,1/tileSize)
	gfx.pop()

	gfx.setTexture(RectMesh)

	local unpackerData = unpackerMap:newImageData()

	if animatePanel.debugView and animatePanel.debugCompaction then
		unpackerData:encode("png", "unpackerData.png")
	end

	local tiles = ffi.new("cpml_vec3[?]", 256 * 256)
	local tileCount = 0
	for y = 0, h, 1 do
		for x = 0, w, 1 do
			local r, g, b, a = unpackerData:getPixel(x, y)
			if a > 0 then
				-- print(x,y,#tiles)
				tileCount = tileCount + 1
				tiles[tileCount].x, tiles[tileCount].y, tiles[tileCount].z = x, y, tileCount
			end
		end
	end

	if tileCount == 0 then
		print("no tiles!")
		return
	end

	-- print(inspect(tiles))
	local maxTileX = math.ceil(math.sqrt(tileCount)) - 1
	local tileStride = maxTileX + 1
	local shrunkH, shrunkW = tileStride * tileSize, tileStride * tileSize

	print("tiles:    ", tileCount)
	print("maxTileX: ", maxTileX)

	local compactCanvas = newCanvas(shrunkW, shrunkH) -- THIS IS THE PACKED TEX
	gfx.setCanvas(compactCanvas)
	gfx.clear(0, 0, 0, 0)

	local compactCanvas2
	if previousCanvas then
		compactCanvas2 = newCanvas(shrunkW, shrunkH)
	end

	local compactorMap = newCanvas(tileStride, tileStride) -- THIS IS FOR PACKING
	gfx.setCanvas(compactorMap)
	gfx.clear(0, 0, 0, 0)

	gfx.setCanvas()

	local compactorData = gfx.newImageData(tileStride, tileStride)
	print("compact size", shrunkW, shrunkH)
	local size1, size2 = dims.w * dims.h, shrunkW * shrunkH
	print(string.format("canvas shrunk %i -> %i, %.1f%% of original size", size1, size2, (size2 / size1) * 100))
	local x, y = 0, 0
	for t = 1, tileCount, 1 do
		local tile = tiles[t]
		-- unpackerData:setPixel(tile.x,tile.y,x/tileStride,y/tileStride,0,1)
		-- compactorData:setPixel(x,y,tile.x/w,tile.y/h,0,1)
		unpackerData:setPixel(tile.x, tile.y, x / 255, y / 255, 0, 1)
		compactorData:setPixel(x, y, tile.x / 255, tile.y / 255, 0, 1)
		y = x == maxTileX and y + 1 or y
		x = x == maxTileX and 0 or x + 1
	end

	if animatePanel.debugView and animatePanel.debugCompaction then
		unpackerData:encode("png", "unpackerData.png")
		compactorData:encode("png", "compactorData.png")
	end

	compactorMap:replacePixels(compactorData)
	compactorMap:setFilter("nearest", "nearest")
	unpackerMap:replacePixels(unpackerData)
	unpackerMap:setFilter("nearest", "nearest")

	gfx.setTexture(RectMesh, compactorMap)

	gfx.push("all")
	gfx.setShader(shaders.mapCanvasTiles)

	shaders.mapCanvasTiles:send("size", tileSize)
	shaders.mapCanvasTiles:send("mapSize", { tileStride, tileStride })
	shaders.mapCanvasTiles:send("sourceSize", { w, h })
	shaders.mapCanvasTiles:send("source", sourceCanvas)

	gfx.setCanvas(compactCanvas)
	gfx.setBlendMode("replace")
	gfx.clear(0, 0, 0, 0)
	gfx.draw(RectMesh, 0, shrunkH, 0, shrunkW, -shrunkH)

	if previousCanvas then
		shaders.mapCanvasTiles:send("source", previousCanvas)
		gfx.setCanvas(compactCanvas2)
		gfx.setBlendMode("replace")
		gfx.clear(0, 0, 0, 0)
		gfx.draw(RectMesh, 0, shrunkH, 0, shrunkW, -shrunkH)
	end

	gfx.pop()

	if animatePanel.debugView and animatePanel.debugCompaction then
		compactCanvas:newImageData():encode("png", "compactCanvas.png")
	end

	print("----------------------\n")

	local packedCanvas = {
		canvas = compactCanvas,
		canvasBefore = compactCanvas2,
		packer = compactorMap,
		unpacker = unpackerMap,
		tileSize = tileSize,
		tileStride = tileStride,
		tileCount = tileCount,
		width = w,
		height = h,
	}

	return packedCanvas
end

function editor_unpackCanvas(packedCanvas, previous, target)
	print("\n-------------------")
	print("unpacking canvas")
	if
		not (
			packedCanvas
			and (packedCanvas.canvas or packedCanvas.canvasBefore)
			and packedCanvas.unpacker
			and packedCanvas.packer
		)
	then
		print("FAILED, no packed canvas")
		print_info(3)
		return
	end
	local width, height =
		(packedCanvas.width + 1) * packedCanvas.tileSize, (packedCanvas.height + 1) * packedCanvas.tileSize
	print("w,h: ", width, height)
	local unpackedCanvas = target or newCanvas(width, height)
	local canvas = previous and packedCanvas.canvasBefore or packedCanvas.canvas
	gfx.setTexture(RectMesh, packedCanvas.unpacker)
	gfx.push("all")
	gfx.setShader(shaders.mapCanvasTiles)
	shaders.mapCanvasTiles:send("size", packedCanvas.tileSize)
	shaders.mapCanvasTiles:send("mapSize", { packedCanvas.width + 1, packedCanvas.height + 1 })
	shaders.mapCanvasTiles:send("sourceSize", { packedCanvas.tileStride, packedCanvas.tileStride })
	shaders.mapCanvasTiles:send("source", canvas)
	gfx.setColor(1, 1, 1, 1)
	gfx.setCanvas(unpackedCanvas)
	if not target then
		gfx.clear(0, 0, 0, 0)
	end
	if target and not canvas then
		gfx.clear(0, 0, 0, 0)
	else
		gfx.setBlendMode("replace")
		gfx.draw(RectMesh, 0, height, 0, width, -height)
	end
	gfx.pop()
	-- unpackedCanvas:newImageData():encode("png","unpacked.png")
	print("-------------------\n")
	return unpackedCanvas
end

function editor_paletteCheck(r, g, b)
	local g = g or r.g
	local b = b or r.b
	local r = type(r) == "table" and r.r or r
	animatePanel.l.palette = animatePanel.l.palette or {}
	for c, color in ipairs(animatePanel.l.palette) do
		if math.abs(color.r - r) < 0.001 and math.abs(color.g - g) < 0.001 and math.abs(color.b - b) < 0.001 then
			return c
		end
	end
	editor_addToPalette(animatePanel, { r = r, g = g, b = b, a = 1 })
	return #animatePanel.l.palette
end

function editor_addToPalette(panel, color)
	if panel.l.format ~= "r8" then
		return
	end
	print("ADDED")
	table.insert(panel.l.palette, deepcopy(color))
end

function editor_drawBrushTexture(panel, canv)
	if panel.brush.texture > 0 then
		-- local img = load_imageTex(text    ures[panel.brush.texture])
		-- gfx.push("all")
		-- gfx.setCanvas (canv)
		-- gfx.setShader ()
		-- gfx.setBlendMode ('multiply')
		-- gfx.setColor (1, 1, 1, 1)
		-- gfx.draw (img, gfx.newQuad(panel.diff.x, panel.diff.y, canv:getWidth(), canv:getHeight(), img:getWidth(), img:getHeight()))
		-- gfx.pop()
		-- gfx.setFilter(canv,panel.filter)
	end
end
