local function getScreenRay(x, y)
	local view = ctx.viewport
	local cam = view.camera
	local x = x
	local y = y
	local dx, dy =
		(x - view.bounds.l - view.bounds.w / 2) / (view.bounds.w / 2),
		(y - view.bounds.t - view.bounds.h / 2) / (view.bounds.h / 2)

	local ax = cam.angle.x
	local ay = cam.angle.y

	local raw = vec3(-1 * view.bounds.w, dy * view.bounds.h, 1000)
	local new = vec3.rotate(vec3.rotate(raw, cam.angle.y, vec3.unit_x), pi - cam.angle.x, vec3.unit_y)

	local p = camera_project((new - cam.pos))
	local d = (p.x - view.bounds.l - view.bounds.w / 2) / (view.bounds.w / 2)
	local mult = 1 / d

	raw = vec3(-dx * view.bounds.w * mult, dy * view.bounds.h * mult, 1000)
	new = vec3.rotate(vec3.rotate(raw, cam.angle.y, vec3.unit_x), pi - cam.angle.x, vec3.unit_y)

	return -cam.pos + vec2.normalize(new) * 1.1, -cam.pos + new * 5
end

function editor_createNewModel(shape)
	editor_drawNewImage(shape)
	editor_createNewModelLayer(shape)
end

function editor_createNewModelLayer(shape)
	local data = {}
	local panel = animatePanel
	lfs.mkdir(ctx.zip and LINK_DIR or GAME_DIR, "symbols", panel.symbol.name, ".models/")

	local id, meshName, texName, skelName = uuid(), "Untitled Mesh", "Untitled Texture", "Untitled Skeleton"

	if shape == "test" then
		data = importIQM(editor.testModel.verts, editor.testModel.map, editor.testModel.tex)

		lfs.mkdir(ctx.zip and LINK_DIR or GAME_DIR, "symbols", panel.symbol.name, ".textures/")
		if editor.testModel.texdir then
			os.execute(
				os.copy
					.. ' "'
					.. editor.testModel.texdir
					.. '" "'
					.. GAME_DIR
					.. "symbols/"
					.. panel.symbol.name
					.. '/.textures/"'
			)
			panel.l.texture = "project"
		end
		if editor.testModel.skeleton then
			game.skeletons[id] = editor.testModel.skeleton
		end
	end

	if shape == "Cube" or not shape then
		local n = 50
		data.vertices = {
			vec3(-n, -n, -n),
			vec3(n, -n, -n),
			vec3(n, -n, n),
			vec3(-n, -n, n),
			vec3(-n, n, -n),
			vec3(n, n, -n),
			vec3(n, n, n),
			vec3(-n, n, n),
		}
		data.edges = {
			{ 1, 2 },
			{ 2, 3 },
			{ 3, 4 },
			{ 4, 1 },
			{ 1, 5 },
			{ 2, 6 },
			{ 3, 7 },
			{ 4, 8 },
			{ 5, 6 },
			{ 6, 7 },
			{ 7, 8 },
			{ 8, 5 },
		}
		data.polygons = {
			{ { 1, 4 }, { 4, 3 }, { 3, 2 }, { 2, 1 } },
			{ { 1, 1 }, { 2, 6 }, { 6, 9 }, { 5, 5 } },
			{ { 2, 2 }, { 3, 7 }, { 7, 10 }, { 6, 6 } },
			{ { 3, 3 }, { 4, 8 }, { 8, 11 }, { 7, 7 } },
			{ { 4, 4 }, { 1, 5 }, { 5, 12 }, { 8, 8 } },
			{ { 5, 9 }, { 6, 10 }, { 7, 11 }, { 8, 12 } },
		}
	end

	if shape == "Cylinder" then
	end

	data.id = id
	panel.symbol.models = panel.symbol.models or {}
	panel.symbol.models[id] = data

	if shape == "test" then
		panel.repose = true
		if editor.testModel.anims then
			local switch
			for a, anim in pairs(editor.testModel.anims) do
				local animation = newAnimation(a, false, true)
				switch = animation.key
				animation.layers[1].data = panel.symbol.models[id]
				animation.layers[1].type = "model"
				animation.layers[1].skeleton = editor.testModel.skeleton --game.skeletons[id]
				for f, frame in ipairs(anim) do
					animation.layers[1].frames[f] = animation.layers[1].frames[f] or editor_newFrame(panel)
					animation.layers[1].frames[f].pose = frame
				end
			end
			print("anid", panel.anims)

			print("anim", switch)
			editor_switchAnimation(switch)
			panel.anims.default = nil
			panel.setDefault = switch
		end
	else
		panel.l.data = panel.symbol.models[id]
		panel.l.type = "model"
	end
end

function importIQM(verts, map, tex)
  local anims
	local vertices, edges, polygons = {}, {}, {}
	local overwriteColors = tex

	local isEdge = function(a, b)
		for e = 1, #edges, 1 do
			if (edges[e][1] == a and edges[e][2] == b) or (edges[e][2] == a and edges[e][1] == b) then
				return e
			end
		end
		return false
	end

	local addEdge = function(p1, p2)
		local edge = isEdge(p1, p2)
		if edge then
			return edge
		end
		table.insert(edges, { p1, p2 })
		return #edges
	end

	for v = 1, #verts, 1 do
		table.insert(vertices, vec3(verts[v]))
	end

	-- print("vec1",verts[1])
	-- print("vec2",vec3(verts[1]))

	local w, h = tex:getDimensions()

	for i = 1, #map, 3 do
		local m1, m2, m3 = map[i], map[i + 1], map[i + 2]
		local e1, e2, e3 = addEdge(m1, m2), addEdge(m2, m3), addEdge(m3, m1)
		local c1, c2, c3
		local u1, u2, u3, v1, v2, v3

		if overwriteColors then
			-- print("verts[m1][5]",verts[m1][5],verts[m1][6])
			-- print(w,h)
			c1, c2, c3 =
				{ tex:getPixel(verts[m1][5] * w, verts[m1][6] * h) },
				{ tex:getPixel(verts[m2][5] * w, verts[m2][6] * h) },
				{ tex:getPixel(verts[m3][5] * w, verts[m3][6] * h) }
			u1, v1 = 1, 1
			u2, v2 = 1, 1
			u3, v3 = 1, 1
		else
			c1, c2, c3 =
				{ verts[m1][7], verts[m1][8], verts[m1][9] },
				{ verts[m2][7], verts[m2][8], verts[m2][9] },
				{ verts[m3][7], verts[m3][8], verts[m3][9] }

			u1, v1 = verts[m1][5], verts[m1][6]
			u2, v2 = verts[m2][5], verts[m2][6]
			u3, v3 = verts[m3][5], verts[m3][6]
		end

		table.insert(polygons, {
			{ m1, e1, u1, v1, c1[1], c1[2], c1[3], 1, unpack(splice(verts[m1], 11)) },
			{ m2, e2, u2, v2, c2[1], c2[2], c2[3], 1, unpack(splice(verts[m2], 11)) },
			{ m3, e3, u3, v3, c3[1], c3[2], c3[3], 1, unpack(splice(verts[m3], 11)) },
		})
	end

	print("polys", #polygons)

	return { vertices = vertices, edges = edges, polygons = polygons }, anims
end

function setMeshReferences(data)
	if type(data) ~= "table" then
		return
	end

	data.refs = data.refs or { nocopy = true }

	data.refs.edges = data.refs.edges or {}
	data.refs.polygons = data.refs.polygons or {}
	data.refs.normals = data.refs.normals or {}
	data.refs.prenorm = data.refs.prenorm or {}
	data.refs.origins = data.refs.origins or {}

	if not vec3.is_vec3(data.vertices[1]) then
		for v, vert in ipairs(data.vertices) do
			data.vertices[v] = vec3(vert)
		end
	end

	for e = 1, #data.edges, 1 do
		data.refs.edges[e] = data.refs.edges[e] or {}
		data.refs.edges[e][1] = data.vertices[data.edges[e][1]]
		data.refs.edges[e][2] = data.vertices[data.edges[e][2]]
	end

	local poly, loop, ref
	for p = 1, #data.polygons, 1 do
		poly = data.polygons[p]
		poly.points = poly.points or { nocopy = true }
		data.refs.polygons[p] = data.refs.polygons[p] or {}
		for l = 1, #poly, 1 do
			loop = poly[l]
			data.refs.polygons[p][l] = data.refs.polygons[p][l] or {}
			ref = data.refs.polygons[p][l]

			ref[1] = data.vertices[loop[1]]
			ref[2] = data.edges[loop[2]]

			if not ref[3] then
				for i = 3, #loop, 1 do
					ref[i] = loop[i]
				end
			end
		end
	end
end

function trisToQuads()
	-- calc normals
	-- create connectivity table
	-- compare shape angles created at edge, choose quad with angles closest to right angles
	-- queue join edge for deletion
	-- remove triangles from polys1
	-- insert new face to polys2
	-- concat polys1+2
end

function updateMeshData(data, edit, panel)
	if edit and not panel.open then
		return
	end

	local hr, hg, hb = 0.9, 0.7, 0.3
	local br, bg, bb = 0, 0, 0

	bench.push("premake")
	if edit then
		local dotVerts = panel.dotVerts
		local lineVerts = panel.lineVerts

		local line

		local check, colorDot = (panel and panel.selectedVerts), false
		for v = 1, #data.vertices, 1 do
			colorDot = check and panel.vertsSelected[v]

			dotVerts[v].x = data.vertices[v].x
			dotVerts[v].y = data.vertices[v].y
			dotVerts[v].z = data.vertices[v].z

			if colorDot then
				dotVerts[v].r, dotVerts[v].g, dotVerts[v].b = hr, hg, hb
			else
				dotVerts[v].r, dotVerts[v].g, dotVerts[v].b = br, bg, bb
			end
		end

		local v, v1, v2, k, color = 0
		for e = 1, #data.edges, 1 do
			if panel and panel.selectedVerts then
				v1, v2 = panel.vertsSelected[data.edges[e][1]], panel.vertsSelected[data.edges[e][2]]
				if v1 and v2 and not inTable(panel.selectedEdges, e) then
					panel.selectedEdges[#panel.selectedEdges] = e
				end
			else
				v1, v2 = false, false
			end

			line = data.refs.edges[e]

			for i = 1, 3, 1 do
				v = v + 1
				k = i == 2 and 2 or 1
				if k == 2 then
					color = v2
				else
					color = v1
				end
				lineVerts[v] = lineVerts[v] or {}
				lineVerts[v].x = line[k].x
				lineVerts[v].y = line[k].y
				lineVerts[v].z = line[k].z

				if color then
					lineVerts[v].r, lineVerts[v].g, lineVerts[v].b = hr, hg, hb
				else
					lineVerts[v].r, lineVerts[v].g, lineVerts[v].b = br, bg, bb
				end
			end
		end

		local poly
		for p = 1, #data.polygons, 1 do
			poly = data.polygons[p]

			if data.refs.origins[p] then
				line = { data.refs.origins[p], data.refs.origins[p] + data.refs.prenorm[p] * 5 }
				-- lineverts not table anymore
				table.insert(lineVerts, { line[1].x, line[1].y, line[1].z, 1, 0, 0, 0, 0, 1 })
				table.insert(lineVerts, { line[2].x, line[2].y, line[2].z, 1, 0, 0, 0, 0, 1 })
				table.insert(lineVerts, { line[1].x, line[1].y, line[1].z, 1, 0, 0, 0, 0, 1 })

				line = { data.refs.origins[p], data.refs.origins[p] + data.refs.normals[p] * 10 }

				table.insert(lineVerts, { line[1].x, line[1].y, line[1].z, 1, 0, 0, 0, 0, 0 })
				table.insert(lineVerts, { line[2].x, line[2].y, line[2].z, 1, 0, 0, 0, 0, 0 })
				table.insert(lineVerts, { line[1].x, line[1].y, line[1].z, 1, 0, 0, 0, 0, 0 })
			end

			local looped = true
			for l = 1, #poly, 1 do
				if not panel.vertsSelected[poly[l][1]] then
					looped = false
				end
			end
			if panel.selectMode[3] and looped and not panel.facesSelected[p] then
				table.add(panel.selectedFaces, p)
			end
		end
	end

	bench.pop("premake")

	bench.push("make verts n tris")
	if not data.tris or not data.verts or data.changed then
		data.tris = { nocopy = true }
		data.verts = { nocopy = true }
	end

	local plane = { position = vec3(), normal = vec3() }
	local angle, axis, rot, rotted = 0, vec3(), mat4(), vec3()
	local vec = vec3()
	local t, v = 0, 0
	local p1, p2, p3
	for p, poly in ipairs(data.refs.polygons) do
		local r, g, b = 1, 1, 1
		local shade = false

		if panel and panel.l and panel.selectedFaces and lume.find(panel.selectedFaces, p) then
			r, g, b = 1, 0.8, 0.4
			shade = true
		end

		if #poly == 3 then
			if poly[1] and poly[1][1] then
				t = t + 1
				data.tris[t] = data.tris[t] or {}

				for i = 1, 3, 1 do
					data.verts[v + i] = data.verts[v + i] or {}
					vert = data.verts[v + i]
					pt = poly[i]
					vert[1], vert[2], vert[3], vert[4] = pt[1].x, pt[1].y, pt[1].z, 1
					data.tris[t][i] = pt[1]
					for a = 3, #pt, 1 do
						vert[a + 2] = pt[a]
					end
					if shade then
						vert[7], vert[8], vert[9] = vert[7] * r + 0.2, vert[8] * g + 0.2, vert[9] * b + 0.2
					end
				end

				data.tris[t][4] = p
				v = v + 3
			end
		else
			poly.shape = poly.shape or {}
			poly.points = poly.points or {}

			lume.clear(poly.shape)
			lume.clear(poly.points)

			for l, loop in ipairs(poly) do
				poly.points[l] = loop[1]
			end

			local checkflip = true
			local flipped = {}
			local flip = false

			local ys
			local keys
			local normal = vec3()

			plane.position, plane.normal = planeFromPoints(poly.points)

			if plane.normal then
				angle = math.acos(axes.y:dot(plane.normal) / (axes.y:len() * plane.normal:len()))
				axis = axes.y:cross(plane.normal)

				local base = angle > 0 and mat4.from_angle_axis(angle, axis) or blankMat

				rot = rot or mat4()
				rot = rot:translate(base, -plane.position)

				ys = {}

				for l, loop in ipairs(poly) do
					rotted = rot * loop[1]
					poly.shape[l * 2 - 1] = rotted.x
					poly.shape[l * 2] = rotted.z
					table.insert(ys, rotted:clone())
				end

				success, triangles = pcall(mth.triangulate, poly.shape)
				if success then
					for _, tri in ipairs(triangles) do
						t = t + 1
						data.tris[t] = {}

						keys, keys2, keys3 = {}

						for i = 1, 3, 1 do
							v = v + 1
							vec.x, vec.y, vec.z = tri[i * 2 - 1], 0, tri[i * 2]
							for j = 1, #ys, 1 do
								if math.sdist(vec.x, vec.z, ys[j].x, ys[j].z) < 0.5 then
									vec.y = ys[j].y
									keys[#keys + 1] = j
									break
								end
							end
							vec = -rot * vec
							data.verts[v] = { vec.x, vec.y, vec.z, 1, 0, 0, r, g, b, 1 }
							data.tris[t][i] = vec:clone()
						end

						keys2 = reverse(lume.sort(keys))
						keys3 = { lume.find(keys2, keys[1]), lume.find(keys2, keys[2]), lume.find(keys2, keys[3]) }

						flipped[1], flipped[2], flipped[3] =
							lume.clone(data.verts[v - 2]), lume.clone(data.verts[v - 1]), lume.clone(data.verts[v])
						data.tris[t] = { data.tris[t][keys3[1]], data.tris[t][keys3[2]], data.tris[t][keys3[3]] }
						normal = cpml.mesh.normal(data.tris[t])
						for i, v in ipairs(flipped) do
							flipped[i][11] = normal.x
							flipped[i][12] = normal.y
							flipped[i][13] = normal.z
						end
						data.verts[v - 2], data.verts[v - 1], data.verts[v] =
							flipped[keys3[1]], flipped[keys3[2]], flipped[keys3[3]]

						data.tris[t][4] = p
					end
				end
			end
		end
	end

	bench.pop("make verts n tris")
end

function editor_meshTools(panel)
	if not panel.l.remodel then
		return
	end
	trace("")
	trace("verts: " .. tostring(#(panel.selectedVerts or empty)) .. " / " .. tostring(#panel.l.data.vertices))
	trace("edges: " .. tostring(#(panel.selectedEdges or empty)) .. " / " .. tostring(#panel.l.data.edges))
	trace("faces: " .. tostring(#(panel.selectedFaces or empty)) .. " / " .. tostring(#panel.l.data.polygons))
	trace("")
	mouse_set(function()
		if not editor.stageHovered then
			return
		end

		if panel.meshTransformMatrix then
			editor_stopMeshTransform(panel)
			return
		end

		panel.selectedVerts = shiftDown and panel.selectedVerts or {}
		panel.selectedEdges = shiftDown and panel.selectedEdges or {}
		panel.selectedFaces = shiftDown and panel.selectedFaces or {}

		local clickedVert = false
		panel.l.updots = true

		if panel.selectMode[1] then
			editor_selectVerts(panel, true)
		end

		if panel.selectMode[3] and editor.intersectTri and editor.intersectTri[4] and not clickedVert then
			local p = editor.intersectTri[4]

			if panel.selectMode[1] then
				for l, loop in ipairs(panel.l.data.polygons[p]) do
					table.add(panel.selectedVerts, loop[1])
				end
			elseif panel.selectMode[2] then
			else
				print(inspect(panel.selectedFaces))
				addrem(panel.selectedFaces, p)
				print(inspect(panel.selectedFaces))
			end
		end
	end)

	if editor.rectSelecting and editor.selectBounds.w > 8 and editor.selectBounds.h > 8 then
		editor_selectVerts(panel)
		if mouse.moved then
			panel.l.updots = true
		end
	end

	panel.selectModeDraws = {
		function()
			gfx.circle("fill", panel.row / 2, panel.row / 2, 2)
		end,
		function()
			gfx.line(6, 6, panel.row - 7, panel.row - 7)
		end,
		function()
			gfx.setLineStyle("rough")
			gfx.rectangle("fill", 5, 5, panel.row - 10, panel.row - 10, 2)
		end,
	}

	gfx.push()
	gfx.translate(0, panel.y - 3)
	for d, draw in ipairs(panel.selectModeDraws) do
		local selected = panel.selectMode[d]
		gfx.push()
		local hovered = false
		if mouse_in({ x = (d - 1) * 18, y = panel.y + 1, w = 16, h = 16 }) then
			hovered = true
			editor_setToolTip(({ "vertices", "edges", "faces" })[d])
			mouse_set(function()
				editor_selectMeshSelectMode(panel, d)
			end)
		end
		gfx.translate((d - 1) * 18, 0)
		gfx.setColor(0.1, 0.1, 0.1, (selected or hovered) and 0.7 or 0.2)
		gfx.rectangle("fill", 2, 2, 16, 16, 4)
		gfx.setColor(1, 1, 1, selected and 1 or 0.1)
		draw()
		gfx.pop()
	end
	gfx.pop()

	for t, toggle in ipairs(panel.meshViewToggles) do
		editor_queueButton({
			x = 16 * 4 + (t - 1) * 18,
			y = panel.y - 2,
			w = panel.row - 2,
			h = panel.row - 2,
			img = toggle,
			tip = toggle,
			imgScaleOverride = 1,
			a = panel[panel.meshViewToggles[t]] and 0.8 or 0.2,
			func = function()
				panel[panel.meshViewToggles[t]] = not panel[panel.meshViewToggles[t]]
			end,
		})
	end

	for o, option in ipairs(panel.meshModeNames) do
		local x = 16 * 8 + (o - 1) * 18
		local img = option .. "_" .. panel[option .. "Mode"]
		panel[option .. "Button"] = {
			tip = option .. " mode",
			w = panel.row - 2,
			h = panel.row - 2,
			x = x,
			y = panel.y - 2,
			img = img,
			imgScaleOverride = 1,
			release = function()
				if not panel.listOpen then
					local items = {}
					local modes = panel[option .. "Modes"]
					table.insert(items, { name = option .. " mode", static = true })
					for m, mode in ipairs(modes) do
						table.insert(
							items,
							{
								{ size = -1, static = true, img = option .. "_" .. mode },
								{
									name = mode,
									func = function()
										panel[option .. "Mode"] = mode
									end,
									col = { a = panel[option .. "Mode"] == mode and 0.5 or 0 },
								},
							}
						)
					end
					editor_openContextMenu(items, { x = x, y = panel.y - (lume.count(modes) + 1.7) * 20, width = 200 })
				end
			end,
		}
		editor_queueButton(panel[option .. "Button"])
	end

	panel.selectedVerts = panel.selectedVerts or {}
	panel.vertsSelected = panel.vertsSelected or {}
	panel.selectedEdges = panel.selectedEdges or {}
	panel.edgesSelected = panel.edgesSelected or {}
	panel.selectedFaces = panel.selectedFaces or {}
	panel.facesSelected = panel.facesSelected or {}

	panel.meshSelections = {
		panel.selectedVerts,
		panel.vertsSelected,
		panel.selectedEdges,
		panel.edgesSelected,
		panel.selectedFaces,
		panel.facesSelected,
	}

	editor.intersectTri = nil

	if panel.selectedVerts[1] then
		trace("selected verts: " .. tostring(panel.l.data.vertices[panel.selectedVerts[1]]))
	end

	for i = 1, #panel.vertsSelected, 1 do
		panel.vertsSelected[i] = false
	end
	for i = 1, #panel.selectedVerts, 1 do
		panel.vertsSelected[panel.selectedVerts[i]] = true
	end

	for i = 1, #panel.facesSelected, 1 do
		panel.facesSelected[i] = false
	end
	for i = 1, #panel.selectedFaces, 1 do
		panel.facesSelected[panel.selectedFaces[i]] = true
	end

	gfx.setColor(1, 1, 1, 1)
	gfx.setLineWidth(4)
	gfx.setLineJoin("bevel")
	editor_updateMeshTransform(panel)
end

function editor_selectVerts(panel, single)
	local size = 8
	local sb = single and { x = mouse.x - size, y = mouse.y - size, w = size * 2, h = size * 2 } or editor.selectBounds
	local n, f = {}, {}

	n.a, f.a = getScreenRay(sb.x, sb.y) -- tl
	n.b, f.b = getScreenRay(sb.x + sb.w, sb.y) -- tr
	n.c, f.c = getScreenRay(sb.x + sb.w, sb.y + sb.h) -- br
	n.d, f.d = getScreenRay(sb.x, sb.y + sb.h) -- bl

	local t = scene_getTransform(panel.instance.models[panel.layer])

	for p, pt in pairs(n) do
		n[p] = -t * n[p]
		f[p] = -t * f[p]
	end

	local frustum = {
		top = cpml.mesh.plane_from_triangle({ f.a, f.b, n.a }),
		bottom = cpml.mesh.plane_from_triangle({ f.c, f.d, n.d }),
		left = cpml.mesh.plane_from_triangle({ f.d, f.a, n.d }),
		right = cpml.mesh.plane_from_triangle({ f.b, f.c, n.b }),
	}

	local contained = true
	local dist = 0
	local closest = (not panel.seeThru and editor.modelIntersect)
			and vec3.dist(editor.modelIntersect, ctx.viewport.camera.pos)
		or 999999
	local close = closest
	local closestVert

	--log(closest)

	for v, vert in ipairs(panel.l.data.vertices) do
		contained = true
		for s, side in pairs(frustum) do
			if contained then
				dist = cpml.mesh.signed_distance(vert, side)
				if dist > 0 then
					contained = false
				end
			end
		end
		if contained then
			if single then
				close = vec3.dist(ctx.viewport.camera.pos, -(t * vert))
				log("close", round(close), round(closest))
				if close < closest + 10 then
					closest = close
					closestVert = v
				end
			else
				table.add(panel.selectedVerts, v)
			end
		end
	end

	if single then
		if shiftDown then
			addrem(panel.selectedVerts, closestVert)
		else
			table.add(panel.selectedVerts, closestVert)
		end
	end
end

function editor_toggleMeshSelect(panel)
	if #panel.selectedVerts == 0 or #panel.selectedEdges == 0 or #panel.selectedVerts == 0 then
		for v, vert in ipairs(panel.l.data.vertices) do
			table.insert(panel.selectedVerts, v)
		end
	else
		editor_deselectMesh(panel)
	end
end

function editor_invertMeshSelect(panel) end

function editor_deselectMesh(panel)
	for s, selection in ipairs(panel.meshSelections) do
		lume.clear(selection)
	end
end

function editor_startMeshTranslate(panel)
	editor_startMeshTransform(panel)
	panel.meshTransform.translation = vec3()
	panel.meshTransform.mode = "Translate"
end

function editor_updateMeshTranslate(panel)
	local p1 = getCameraPlaneCoords(
		panel.meshTransform.origin,
		camera_cursorRay(panel.meshTransform.mouse.x, panel.meshTransform.mouse.y)
	)
	local p2 = getCameraPlaneCoords(panel.meshTransform.origin, camera_cursorRay(mouse.x, mouse.y))
	if not p1 then
		return
	end
	panel.meshTransform.translation = p1 - p2
	panel.meshTransformMatrix =
		panel.meshTransformMatrix:translate(blankMat, panel.instance.orientation * panel.meshTransform.translation)
	local screenOrigin =
		camera_project(-panel.instance.transform * -panel.meshTransformMatrix * panel.meshTransform.origin)
	gfx.line(panel.meshTransform.screenOrigin.x, panel.meshTransform.screenOrigin.y, screenOrigin.x, screenOrigin.y)
end

function editor_startMeshRotate(panel)
	editor_startMeshTransform(panel)
	panel.meshTransform.rotation = 0
	panel.meshTransform.mode = "Rotate"
end

function editor_updateMeshRotate(panel)
	local screenAngle = vec2.angle_to(panel.meshTransform.screenOrigin, mouse)
	local angle = screenAngle - panel.meshTransform.screenAngle + pi * 2
	local diff = angDiff(screenAngle, panel.meshTransform.screenAngle)

	local e1, e2 = panel.meshTransform.screenAngle, panel.meshTransform.screenAngle + angle
	local a1, a2 = math.min(e1, e2), math.max(e1, e2)

	local az = ctx.viewport.camera.pos - panel.meshTransform.origin
	local axis = ctx.viewport.camera.lookDir

	--gfx.circle("line",panel.meshTransform.screenOrigin.x,panel.meshTransform.screenOrigin.y,32)
	gfx.push()
	gfx.translate(panel.meshTransform.screenOrigin.x, panel.meshTransform.screenOrigin.y)
	gfx.rotate(panel.meshTransform.screenAngle + pi)
	gfx.arc("line", 0, 0, 30, 0, -diff)
	gfx.circle("line", 0, 0, 30)
	gfx.pop()
	gfx.line(panel.meshTransform.screenOrigin.x, panel.meshTransform.screenOrigin.y, mouse.x, mouse.y)

	panel.meshTransformMatrix = panel.meshTransformMatrix:translate(
		panel.meshTransformMatrix:rotate(
			panel.meshTransformMatrix:translate(blankMat, panel.meshTransform.origin),
			-angle,
			axis
		),
		-panel.meshTransform.origin
	)
end

function editor_startMeshScale(panel)
	editor_startMeshTransform(panel)
	panel.meshTransform.scale = vec3(1)
	panel.meshTransform.mode = "Scale"
end

function editor_updateMeshScale(panel)
	local dist = vec2.dist(panel.meshTransform.screenOrigin, mouse)
	panel.meshTransform.scale = vec3(dist / panel.meshTransform.screenDist)

	gfx.circle("line", panel.meshTransform.screenOrigin.x, panel.meshTransform.screenOrigin.y, 8)
	gfx.line(panel.meshTransform.screenOrigin.x, panel.meshTransform.screenOrigin.y, mouse.x, mouse.y)

	panel.meshTransformMatrix = panel.meshTransformMatrix:translate(
		panel.meshTransformMatrix:scale(
			panel.meshTransformMatrix:translate(blankMat, panel.meshTransform.origin),
			panel.meshTransform.scale
		),
		-panel.meshTransform.origin
	)
end

function editor_startMeshInset(panel)
	editor_startMeshTransform(panel)
	local center = {}
	local faceNormal = vec3(0, 1, 0)
	local edgeNormal, vertNormal
	panel.meshTransform.mode = "Inset"
	for f, face in ipairs(panel.selectedFaces) do
		local poly = panel.l.data.refs.polygons[face]
		local poly2 = panel.l.data.polygons[face]
		local prevEdgeDir = poly[#poly][2][2] - poly[#poly][1]
		for l, loop in ipairs(poly) do
			local edgeDir = loop[2][2] - loop[1]
			vertNormal = vec3.cross(prevEdgeDir + edgeDir)
			local newVertex = loop[2][2] + vertNormal * 5

			table.insert(panel.l.data.vertices, newVertex)
			table.insert(panel.l.data.edges, { poly2[face][l][1], #panel.l.data.vertices })

			if l > 1 then
				table.insert(panel.l.data.edges, { #panel.l.data.vertices - 1, #panel.l.data.vertices })
				table.insert(panel.l.data.polygons[face], { poly2[face][l][1], #panel.l.data.edges })
			end

			table.insert(panel.l.data.polygons[face], { poly2[face][l][1], #panel.l.data.edges })
			table.insert(panel.l.data.polygons[face], { poly2[face][l][1], #panel.l.data.edges })

			prevEdgeDir = edgeDir
		end
	end
end

function editor_updateMeshInset(panel) end

function editor_startMeshExtrude(panel)
	panel.pushUndoState()
	panel.selectedVerts = {}

	for f, face in ipairs(panel.selectedFaces) do
		local poly = panel.l.data.polygons[face]
		local edges = panel.l.data.edges
		local firstVert = #panel.l.data.vertices + 1
		local firstEdge = #panel.l.data.edges + 1
		local topFace = {}
		local sideFaces = {}

		for l, loop in ipairs(poly) do
			table.insert(panel.l.data.vertices, panel.l.data.vertices[loop[1]]:clone())
			table.insert(panel.selectedVerts, #panel.l.data.vertices)
			table.insert(panel.l.data.edges, { loop[1], #panel.l.data.vertices })
			table.insert(
				panel.l.data.edges,
				{ #panel.l.data.vertices, l == #poly and firstVert or #panel.l.data.vertices + 1 }
			)

			table.insert(topFace, { #panel.l.data.vertices, #panel.l.data.edges })

			local sideFace = {}

			table.insert(
				sideFace,
				{ poly[l < #poly and l + 1 or 1][1], l < #poly and #panel.l.data.edges + 1 or poly[1][2] }
			)
			table.insert(sideFace, { l == #poly and firstVert or #panel.l.data.vertices + 1, #panel.l.data.edges })
			table.insert(sideFace, { #panel.l.data.vertices, #panel.l.data.edges - 1 })
			table.insert(sideFace, { loop[1], poly[l < #poly and l + 1 or 1][2] })

			table.insert(sideFaces, sideFace)
		end

		table.remove(panel.l.data.polygons, face)
		table.insert(panel.l.data.polygons, topFace)

		for s = 1, #sideFaces, 1 do
			table.insert(panel.l.data.polygons, sideFaces[s])
		end
	end

	panel.selectedFaces = {}

	editor_startMeshTranslate(panel)
end

function oppositeVertex(v, e)
	return v == e[1] and e[2] or e[1]
end

function editor_startMeshTransform(panel)
	if not input_keyPressed("e") then
		panel.pushUndoState()
	end

	for a, axis in ipairs({ "x", "y", "z" }) do
		if input_keyPressed(axis) then
			if panel.axisName == axis then
				local baseAxis = axes[axis]
			else
				panel.axisName = axis
			end

			if panel.transformMode == "global" then
				panel.axis = baseAxis
			elseif panel.transformMode == "local" then
				panel.axis = baseAxis
			elseif panel.transformMode == "normal" then
				panel.axis = baseAxis
			end
		end
	end

	panel.meshTransformMatrix = mat4()
	panel.meshTransform = { mouse = vec2(mouse) }
	panel.selectedVertices = panel.selectedVertices or {}
	lume.clear(panel.selectedVertices)

	if panel.selectMode[3] and #panel.selectedFaces > 0 then
		for f, face in ipairs(panel.selectedFaces) do
			for l, loop in ipairs(panel.l.data.polygons[face]) do
				if panel.selectedVerts and loop[1] then
					table.add(panel.selectedVerts, loop[1])
				end
			end
		end
	end

	local sum = vec3()
	for v, vert in ipairs(panel.selectedVerts) do
		panel.selectedVertices[v] = panel.l.data.vertices[vert]:clone()
		sum = sum + panel.selectedVertices[v]
	end

	panel.meshTransform.origin = sum / #panel.selectedVerts
	panel.meshTransform.screenOrigin = camera_project(-panel.instance.transform * panel.meshTransform.origin)
	panel.meshTransform.screenDist = vec2.dist(panel.meshTransform.screenOrigin, mouse)
	panel.meshTransform.screenAngle = vec2.angle_to(panel.meshTransform.screenOrigin, mouse)
end

function editor_updateMeshTransform(panel)
	if not panel.meshTransformMatrix then
		return
	end

	editor["updateMesh" .. panel.meshTransform.mode](panel)

	local a, b = panel.meshTransformMatrix
	for v, vert in ipairs(panel.selectedVerts) do
		b = panel.selectedVertices[v]
		if b then
			--trace(panel.l.data.vertices[v])
			panel.l.data.vertices[vert].x = b.x * a[1] + b.y * a[5] + b.z * a[9] + a[13]
			panel.l.data.vertices[vert].y = b.x * a[2] + b.y * a[6] + b.z * a[10] + a[14]
			panel.l.data.vertices[vert].z = b.x * a[3] + b.y * a[7] + b.z * a[11] + a[15]
		end
	end

	panel.transformer.visible = false
end

function editor_stopMeshTransform(panel)
	panel.meshTransformMatrix = nil
	panel.meshTransform.mode = nil
	panel.l.updots = true
end

function editor_selectMeshSelectMode(panel, i)
	if shiftDown then
		if not panel.selectMode[i] or lume.count(panel.selectMode, function(x)
			return x
		end) > 1 then
			panel.selectMode[i] = not panel.selectMode[i]
		end
	else
		panel.selectMode = { false, false, false }
		panel.selectMode[i] = true
	end
	panel.l.updots = true
end
