--[[

WARNING!
This script requires AE_Utilities and AE_Curvature!
http://www.revival.ru/test/ae_curvature.lua

]]


-- **************************************************
-- Provide Moho with the name of this script object
-- **************************************************

ScriptName = "AE_TransformPoints"

-- **************************************************
-- General information about this script
-- **************************************************

AE_TransformPoints = {}

AE_TransformPoints.BASE_STR = 2375

function AE_TransformPoints:Name()
	return "Transform Points"
end

function AE_TransformPoints:Version()
	return "10.08"
end

function AE_TransformPoints:IsBeginnerScript()
	return true
end

function AE_TransformPoints:Description()
	return string.format(
		MOHO.Localize("/Scripts/Tool/TransformPoints/Description=Move/Scale/Rotate selected points (press <%s> to weld, hold <shift> to constrain, <alt> to disable auto-welding, <alt> to scale to center, <shift> while scaling to squash, <ctrl/cmd> to select points)"),
		MOHO.BindingKeyName()
	)
end

function AE_TransformPoints:BeginnerDescription()
	return MOHO.Localize("/Scripts/Tool/TransformPoints/BeginnerDescription=Click in the center of your object to select all points, allowing you to move the entire object. If you only select and drag one point or multiple points, you can distort the object. Move points around in time to animate them.")
end

function AE_TransformPoints:BeginnerDisabledDescription()
	return MOHO.Localize("/Scripts/Tool/TransformPoints/BeginnerDisabledDescription=You need to create a vector drawing on a Vector Layer first with the 'Add Point', 'Freehand' or 'Draw Shape' tool before being able to use this tool. To add a new vector layer, go to the Layers window and select New Layer > Vector.")
end

function AE_TransformPoints:Creator()
	return "Smith Micro Software, Inc., improved by Alexandra Evseeva, modified by Stan"
end

function AE_TransformPoints:UILabel()
	return(MOHO.Localize("/Scripts/Tool/TransformPoints/TransformPoints=Transform Points"))
end

function AE_TransformPoints:LoadPrefs(prefs)
	self.autoWeld = prefs:GetBool("AE_TransformPoints.autoWeld", false)
	self.autoFill = prefs:GetBool("AE_TransformPoints.autoFill", true)
	self.autoStroke = prefs:GetBool("AE_TransformPoints.autoStroke", true)
	self.showHandles = prefs:GetBool("AE_TransformPoints.showHandles", false)
	self.fixedHandles = prefs:GetBool("AE_TransformPoints.fixedHandles", false)
end

function AE_TransformPoints:SavePrefs(prefs)
	prefs:SetBool("AE_TransformPoints.autoWeld", self.autoWeld)
	prefs:SetBool("AE_TransformPoints.autoFill", self.autoFill)
	prefs:SetBool("AE_TransformPoints.autoStroke", self.autoStroke)
	prefs:SetBool("AE_TransformPoints.showHandles", self.showHandles)
	prefs:SetBool("AE_TransformPoints.fixedHandles", self.fixedHandles)
end

function AE_TransformPoints:ResetPrefs()
	AE_TransformPoints.autoWeld = false
	AE_TransformPoints.autoWeldRadius = 12
	AE_TransformPoints.autoFill = true
	AE_TransformPoints.autoStroke = true
	AE_TransformPoints.showHandles = false
	AE_TransformPoints.fixedHandles = true
end

function AE_TransformPoints:NonDragMouseMove()
	return true -- Call MouseMoved() even if the mouse button is not down
end

-- **************************************************
-- Recurring values
-- **************************************************

AE_TransformPoints.dragging = false
AE_TransformPoints.keyMovement = false
AE_TransformPoints.mode = 0 -- 0:translate, 1:rotate, 2:top left scale, 3:top right scale, 4: bottom left scale, 5: bottom right scale, 6:left scale, 7:right scale, 8:top scale, 9:bottom scale, 10:bezier handle, 100:select points
AE_TransformPoints.numSel = 0
AE_TransformPoints.selID = -1
AE_TransformPoints.fillingShape = false
AE_TransformPoints.endWeldVec = LM.Vector2:new_local()
AE_TransformPoints.endWeldVec:Set(-10000000, -10000000)
AE_TransformPoints.endWeldToPoint = true
AE_TransformPoints.startAngle = 0
AE_TransformPoints.lastVec = LM.Vector2:new_local()
AE_TransformPoints.selectMode = 100

-- **************************************************
-- The guts of this script
-- **************************************************

function AE_TransformPoints:IsEnabled(moho)
	if (moho:CountPoints() > 0) then
		return true
	end
	return false
end

function AE_TransformPoints:IsRelevant(moho)
	local mesh = moho:DrawingMesh()
	if (mesh == nil) then
		return false
	end
	return true
end

function AE_TransformPoints:SelectedMeshBounds(moho, layer, mesh, frame, view)
	local bbox = LM.BBox:new_local()
	local min = LM.Vector2:new_local()
	local max = LM.Vector2:new_local()
	mesh:SelectedBounds(min, max)
	bbox.fMin:Set(min.x, min.y, 0)
	bbox.fMax:Set(max.x, max.y, 0)

	-- make sure the bounding box is not too thin in one direction
	local xLen = bbox.fMax.x - bbox.fMin.x
	local yLen = bbox.fMax.y - bbox.fMin.y
	if (xLen < yLen / 10.0) then
		local center = (bbox.fMin.x + bbox.fMax.x) / 2.0
		bbox.fMin.x = center - yLen / 10.0
		bbox.fMax.x = center + yLen / 10.0
	elseif (yLen < xLen / 10.0) then
		local center = (bbox.fMin.y + bbox.fMax.y) / 2.0
		bbox.fMin.y = center - xLen / 10.0
		bbox.fMax.y = center + xLen / 10.0
	end

	-- make sure the bounding box is not too small
	local minLength = 150
	local m = LM.Matrix:new_local()
	local v = LM.Vector2:new_local()
	local pt1 = LM.Point:new_local()
	local pt2 = LM.Point:new_local()

	layer:GetFullTransform(frame, m, moho.document)
	v:Set(bbox.fMin.x, bbox.fMin.y)
	m:Transform(v)
	view:Graphics():WorldToScreen(v, pt1)
	v:Set(bbox.fMax.x, bbox.fMax.y)
	m:Transform(v)
	view:Graphics():WorldToScreen(v, pt2)
	pt1 = pt2 - pt1
	local length = math.sqrt(pt1.x * pt1.x + pt1.y * pt1.y)
	if (length < minLength) then
		center = (bbox.fMin + bbox.fMax) / 2.0
		v = bbox.fMax - center
		bbox.fMax = center + v * (minLength / length) / 2.0
		v = bbox.fMin - center
		bbox.fMin = center + v * (minLength / length) / 2.0
	end

	return bbox
end

function AE_TransformPoints:TestMousePoint(moho, mouseEvent)
	-- Returns what mode the tool would be in if the user clicked at the current mouse location
	if (self.keyMovement) then
		return 0
	end

	local mesh = moho:DrawingMesh()
	if (mesh == nil) then
		return 0
	end

	self.numSel = moho:CountSelectedPoints()
	if (self.numSel < 2) then
		return 0
	end

	local markerR = 4
	local bbox = self:SelectedMeshBounds(moho, moho.drawingLayer, mesh, moho.drawingFrame, mouseEvent.view)
	local v = LM.Vector2:new_local()
	local pt = LM.Point:new_local()
	local m = LM.Matrix:new_local()

	moho.drawingLayer:GetFullTransform(moho.frame, m, moho.document)

	-- test for uniform scaling
	v:Set(bbox.fMin.x, bbox.fMin.y)
	m:Transform(v)
	mouseEvent.view:Graphics():WorldToScreen(v, pt)
	if (math.abs(pt.x - mouseEvent.pt.x) < markerR and math.abs(pt.y - mouseEvent.pt.y) < markerR) then
		-- bottom left
		return 4
	end
	v:Set(bbox.fMin.x, bbox.fMax.y)
	m:Transform(v)
	mouseEvent.view:Graphics():WorldToScreen(v, pt)
	if (math.abs(pt.x - mouseEvent.pt.x) < markerR and math.abs(pt.y - mouseEvent.pt.y) < markerR) then
		-- top left
		return 2
	end
	v:Set(bbox.fMax.x, bbox.fMax.y)
	m:Transform(v)
	mouseEvent.view:Graphics():WorldToScreen(v, pt)
	if (math.abs(pt.x - mouseEvent.pt.x) < markerR and math.abs(pt.y - mouseEvent.pt.y) < markerR) then
		-- top right
		return 3
	end
	v:Set(bbox.fMax.x, bbox.fMin.y)
	m:Transform(v)
	mouseEvent.view:Graphics():WorldToScreen(v, pt)
	if (math.abs(pt.x - mouseEvent.pt.x) < markerR and math.abs(pt.y - mouseEvent.pt.y) < markerR) then
		-- bottom right
		return 5
	end

	-- test for X scaling
	v:Set(bbox.fMin.x, (bbox.fMin.y + bbox.fMax.y) * 0.5)
	m:Transform(v)
	mouseEvent.view:Graphics():WorldToScreen(v, pt)
	if (math.abs(pt.x - mouseEvent.pt.x) < markerR and math.abs(pt.y - mouseEvent.pt.y) < markerR) then
		-- left
		return 6
	end
	v:Set(bbox.fMax.x, (bbox.fMin.y + bbox.fMax.y) * 0.5)
	m:Transform(v)
	mouseEvent.view:Graphics():WorldToScreen(v, pt)
	if (math.abs(pt.x - mouseEvent.pt.x) < markerR and math.abs(pt.y - mouseEvent.pt.y) < markerR) then
		-- right
		return 7
	end
	
	-- test for Y scaling
	v:Set((bbox.fMin.x + bbox.fMax.x) * 0.5, bbox.fMin.y)
	m:Transform(v)
	mouseEvent.view:Graphics():WorldToScreen(v, pt)
	if (math.abs(pt.x - mouseEvent.pt.x) < markerR and math.abs(pt.y - mouseEvent.pt.y) < markerR) then
		-- bottom
		return 9
	end
	v:Set((bbox.fMin.x + bbox.fMax.x) * 0.5, bbox.fMax.y)
	m:Transform(v)
	mouseEvent.view:Graphics():WorldToScreen(v, pt)
	if (math.abs(pt.x - mouseEvent.pt.x) < markerR and math.abs(pt.y - mouseEvent.pt.y) < markerR) then
		-- top
		return 8
	end

	-- test for translation outside the bounding box
	local rotWidth = bbox.fMax.x - bbox.fMin.x
	if (bbox.fMax.y - bbox.fMin.y > rotWidth) then
		rotWidth = bbox.fMax.y - bbox.fMin.y
	end
	rotWidth = rotWidth * 0.1
	if (mouseEvent.drawingVec.x < bbox.fMin.x - rotWidth or mouseEvent.drawingVec.x > bbox.fMax.x + rotWidth or mouseEvent.drawingVec.y < bbox.fMin.y - rotWidth or mouseEvent.drawingVec.y > bbox.fMax.y + rotWidth) then
		return 0
	end
	
	-- test for rotation
	if (mouseEvent.drawingVec.x < bbox.fMin.x or mouseEvent.drawingVec.x > bbox.fMax.x or mouseEvent.drawingVec.y < bbox.fMin.y or mouseEvent.drawingVec.y > bbox.fMax.y) then
		return 1
	end

	return 0 -- translation inside the bounding box
end

function AE_TransformPoints:OnMouseDown(moho, mouseEvent)
	local mesh = moho:DrawingMesh()
	if (mesh == nil) then
		return
	end
	
	self:PopulateSelList(moho, mesh)
		
	if (mouseEvent.altKey) and (mouseEvent.shiftKey) then
		if not self.customCenter then self.customCenter = mesh:SelectedCenter() end
		self.customCenter:Set(mouseEvent.drawingStartVec)
		if self.centerMode then self.centerVec:Set(self.customCenter) end
		return
	end		
	
	self.dragging = true

	self.endWeldVec:Set(-10000000, -10000000)

	self.mode = self:TestMousePoint(moho, mouseEvent)
	if (mouseEvent.ctrlKey) then
		self.mode = self.selectMode
		mouseEvent.ctrlKey = false
		LM_SelectPoints:OnMouseDown(moho, mouseEvent)
		return
	end
	if not self.centerMode then self.centerVec:Set(mesh:SelectedCenter()) else self.centerVec:Set(self.customCenter) end
	self.selectedMin = LM.Vector2:new_local()
	self.selectedMax = LM.Vector2:new_local()
	mesh:SelectedBounds(self.selectedMin, self.selectedMax)
	moho.document:SetDirty()

	self.numSel = moho:CountSelectedPoints()

	if (self.selID == -1) then -- if this isn't true, then a point has already been selected (maybe by the add point tool)
		if (self.mode == 0 and self.numSel < 2 and self.showHandles) then
			AE_Curvature:TestForClosestHandle(moho, mouseEvent)
			if (AE_Curvature.selID >= 0 and AE_Curvature.handleSide ~= 0) then -- a bezier handle was selected
				self.mode = 10
				local point = mesh:Point(AE_Curvature.selID)
				if (point:IsEndpoint()) then
					-- If working with a curve endpoint, and its handles have not been extended, don't do it with this tool.
					-- It's preferable to just move the endpoint.
					-- If the user wants to move the handle, they must use the curvature tool.
					local curve = nil
					local ptPos = -1
					curve, ptPos = point:Curve(0, ptPos)
					local weight = curve:GetWeight(ptPos, moho.drawingFrame, AE_Curvature.handleSide == -1)
					local offset = curve:GetOffset(ptPos, moho.drawingFrame, AE_Curvature.handleSide == -1)
					local wDiff = weight - 1.0
					local oDiff = offset - 0.0
					if (math.abs(wDiff) < 0.0001 and math.abs(oDiff) < 0.0001 and curve:CountPoints() > 2) then
						self.mode = 0
					end
				end
			end
			self.selID = -1
			self.handleSide = 0
		end
		moho.document:PrepUndo(moho.drawingLayer)
	end

	if (self.mode == 0) then -- translate
		if (self.selID == -1) then
			local shiftKey = mouseEvent.shiftKey
			local ctrlKey = mouseEvent.ctrlKey
			local altKey = mouseEvent.altKey
			mouseEvent.shiftKey = false
			mouseEvent.ctrlKey = false
			mouseEvent.altKey = false
			mouseEvent.shiftKey = altKey
			mouseEvent.ctrlKey = altKey
			mouseEvent.altKey = altKey
			self.numSel = moho:CountSelectedPoints(true)

			if (self.numSel < 2) then -- move just a single point
				-- find the closest point here
				self.selID = mesh:ClosestPoint(mouseEvent.drawingStartVec)
				if (self.selID >= 0) then
					self.numSel = 1
					mesh:SelectNone()
					mesh:Point(self.selID).fSelected = true
					moho:UpdateSelectedChannels()
				end
			else
				self.selID = mesh:ClosestPoint(mouseEvent.drawingStartVec)
			end
		end
	elseif (self.mode == 1) then -- rotate
		local shiftKey = mouseEvent.shiftKey
		local ctrlKey = mouseEvent.ctrlKey
		local altKey = mouseEvent.altKey
		mouseEvent.shiftKey = false
		mouseEvent.ctrlKey = false
		mouseEvent.altKey = false
		mouseEvent.shiftKey = altKey
		mouseEvent.ctrlKey = altKey
		mouseEvent.altKey = altKey
		self.numSel = moho:CountSelectedPoints(true)
		self.startAngle = 0
		self.lastVec:Set(mouseEvent.drawingVec)
	elseif (self.mode == 10) then -- bezier handle
		AE_Curvature:OnMouseDown(moho, mouseEvent)
		return
	else -- scale
		self.lastScaleX = 1.0
		self.lastScaleY = 1.0

		if (self.numSel < 2) then
			local shiftKey = mouseEvent.shiftKey
			local ctrlKey = mouseEvent.ctrlKey
			local altKey = mouseEvent.altKey
			mouseEvent.shiftKey = false
			mouseEvent.ctrlKey = false
			mouseEvent.altKey = false
			mouseEvent.shiftKey = altKey
			mouseEvent.ctrlKey = altKey
			mouseEvent.altKey = altKey
			self.numSel = moho:CountSelectedPoints(true)
		end

		if (self.numSel < 2) then
			mouseEvent.view:DrawMe()
			return
		end
	end

	mesh:PrepMovePoints()
	if (self:UseFixedHandles(moho)) then
		mesh:PrepFixedHandles()
		self:FixHandles(moho, mesh)
	end

	self:PopulateSelList(moho, mesh)
	
	mouseEvent.view:DrawMe()
end

function AE_TransformPoints:PopulateSelList(moho, mesh)
	self.fPoses = {}
	self.selList = {}
	self.inOffsets = {}
	self.outOffsets = {}
	for i = 0, mesh:CountPoints() - 1 do
		local pt = mesh:Point(i)
		if pt.fSelected then 
			table.insert(self.selList, pt)
			table.insert(self.fPoses, LM.Vector2:new_local())
			local ins = {}
			local outs = {}
			for c = 0, pt:CountCurves() -1 do
				local curve, where = pt:Curve(c)
				ins[c] = curve:GetOffset(where, moho.drawingFrame, true)
				outs[c] = curve:GetOffset(where, moho.drawingFrame, false)			
			end
			table.insert(self.inOffsets, ins)
			table.insert(self.outOffsets, outs)
		end
	end
end

function AE_TransformPoints:OnMouseMoved(moho, mouseEvent)
	local mesh = moho:DrawingMesh()
	if (mesh == nil) then
		return
	end
	
	if (mouseEvent.altKey) and (mouseEvent.shiftKey) then
		return
	end		



	if (not self.dragging) then
		local mode = self:TestMousePoint(moho, mouseEvent)

		if (mode == 0) then
			mouseEvent.view:SetCursor(MOHO.moveCursor)
		elseif (mode == 1) then
			mouseEvent.view:SetCursor(MOHO.rotateCursor)
		else
			mouseEvent.view:SetCursor(MOHO.scaleCursor)
		end
		mouseEvent.view:DrawMe()
		return
	end

	if (self.mode == self.selectMode) then
		mouseEvent.ctrlKey = false
		LM_SelectPoints:OnMouseMoved(moho, mouseEvent)
	elseif (self.mode == 0) then -- translate
		self:OnMouseMoved_T(moho, mouseEvent)
	elseif (self.mode == 1) then -- rotate
		self:OnMouseMoved_R(moho, mouseEvent)
	elseif (self.mode == 10) then -- bezier handle
		AE_Curvature:OnMouseMoved(moho, mouseEvent)
		return
	else -- scale
		self:OnMouseMoved_S(moho, mouseEvent)
	end
	
	--[[
	for i, pt in pairs(self.selList) do
		self.fPoses[i] = pt.fPos
	end
	--]]
end

function AE_TransformPoints:OnMouseMoved_T(moho, mouseEvent)
	local mesh = moho:DrawingMesh()
	if (mesh == nil) then
		return
	end

	if (mouseEvent.ctrlKey) then -- hold down the control key to just select a single point and not move it
		return
	end

	self.endWeldVec:Set(-10000000, -10000000)

	local curVec = mouseEvent.drawingVec - mouseEvent.drawingStartVec
	if (mouseEvent.shiftKey) then
		if (math.abs(mouseEvent.drawingVec.x - mouseEvent.drawingStartVec.x) > math.abs(mouseEvent.drawingVec.y - mouseEvent.drawingStartVec.y)) then
			curVec.y = 0
		else
			curVec.x = 0
		end
	end
	if (self.numSel < 2) then -- just working with 1 point
		if (self.selID == -1) then
			return
		end
		local pt = mesh:Point(self.selID)
		pt.fPos:Set(pt.fTempPos + curVec)
		if (moho.gridOn) then
			moho:SnapToGrid(pt.fPos)
		end
		if (not(mouseEvent.altKey) and self.autoWeld and pt:IsEndpoint()) then
			-- Display the upcoming weld point for the user's benefit
			local m = LM.Matrix:new_local()
			local testVec1 = LM.Vector2:new_local()
			local p1 = LM.Point:new_local()
			local testVec2 = LM.Vector2:new_local()
			local p2 = LM.Point:new_local()
			local dist = 0
			local curveID = -1
			local segID = -1
			local pickWidth = 3

			self.endWeldVec:Set(-10000000, -10000000)
			testVec1:Set(pt.fPos)
			moho.drawingLayer:GetFullTransform(moho.frame, m, moho.document)
			m:Transform(testVec1)
			moho.view:Graphics():WorldToScreen(testVec1, p1)

			-- first try to find another point to weld this one to
			local closestID = mesh:ClosestPoint(pt.fPos, self.selID)
			if (closestID >= 0) then
				testVec2:Set(mesh:Point(closestID).fPos)
				m:Transform(testVec2)
				moho.view:Graphics():WorldToScreen(testVec2, p2)
				dist = (p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y)
				if (dist < self.autoWeldRadius * self.autoWeldRadius and (not mesh:ArePointsAdjacent(self.selID, closestID))) then
					self.endWeldToPoint = true
					self.endWeldVec:Set(mesh:Point(closestID).fPos)
				else
					while (pickWidth < self.autoWeldRadius) do
						curveID, segID = pt:GetEndpointEdge(curveID, segID)
						curveID, segID = moho.view:PickEdge(p1, curveID, segID, pickWidth)
						if (curveID >= 0) then -- add a point in the middle of some curve
							if ((not mesh:Curve(curveID):IsPointOnSegment(self.selID, segID)) and
								(not mesh:Curve(curveID):IsPointOnSegment(self.selID, segID - 1)) and
								(not mesh:Curve(curveID):IsPointOnSegment(self.selID, segID + 1))) then -- don't weld the point back on itself
								self.endWeldToPoint = false
								self.endWeldVec:Set(mesh:Curve(curveID):ClosestPointOnSegment(segID, pt.fPos, true, true))
								break
							end
						end
						pickWidth = pickWidth + 3
					end -- while
				end
			end
		end
	else -- move multiple points
		if (moho.gridOn) then
			if (self.selID > -1) then
				local pt = mesh:Point(self.selID)
				local tempVec = pt.fTempPos + curVec
				moho:SnapToGrid(tempVec)
				curVec:Set(tempVec - pt.fTempPos)
			else
				moho:SnapToGrid(curVec)
			end
		end
		mesh:TranslatePoints(curVec)
	end

	--TODO:apply all final calculations here, to use onion skin (uncomment next line and call a universal method to fix delta)
	--moho:AddPointKeyframe(moho.drawingFrame)
	self:AddPointKeyframe(moho, moho.drawingFrame)

	if (self:UseFixedHandles(moho)) then
		mesh:PreserveHandlePositions()
	end

	mouseEvent.view:DrawMe()
end

function AE_TransformPoints:OnMouseMoved_S(moho, mouseEvent)
	if (self.numSel < 2) then
		return
	end

	local mesh = moho:DrawingMesh()
	if (mesh == nil) then
		return
	end

	local center = LM.Vector2:new_local()
	center:Set(self.centerVec)
	if (mouseEvent.altKey) then
		center:Set(self.centerVec)
	else
		if (self.mode == 6) then -- LEFT
			center:Set(self.selectedMax.x, (self.selectedMin.y + self.selectedMax.y) / 2.0)
		elseif (self.mode == 7) then -- RIGHT
			center:Set(self.selectedMin.x, (self.selectedMin.y + self.selectedMax.y) / 2.0)
		elseif (self.mode == 8) then -- TOP
			center:Set((self.selectedMin.x + self.selectedMax.x) / 2.0, self.selectedMin.y)
		elseif (self.mode == 9) then -- BOTTOM
			center:Set((self.selectedMin.x + self.selectedMax.x) / 2.0, self.selectedMax.y)
		elseif (self.mode == 4) then -- BL
			center:Set(self.selectedMax.x, self.selectedMax.y)
		elseif (self.mode == 2) then -- TL
			center:Set(self.selectedMax.x, self.selectedMin.y)
		elseif (self.mode == 3) then -- TR
			center:Set(self.selectedMin.x, self.selectedMin.y)
		elseif (self.mode == 5) then -- BR
			center:Set(self.selectedMin.x, self.selectedMax.y)	
		end
	end

	local scaling = LM.Vector2:new_local()

	scaling:Set(1, 1)
	-- scaling connected to actual drag amount
	local v1 = mouseEvent.drawingStartVec - center
	local v2 = mouseEvent.drawingVec - center
	scaling.x = v2.x / v1.x
	scaling.y = v2.y / v1.y

	if (self.mode == 6 or self.mode == 7) then
		-- horizontal scaling
		scaling.y = 1
		if (mouseEvent.shiftKey) then
			scaling.y = 1 / scaling.x
		end
	elseif (self.mode == 8 or self.mode == 9) then
		-- vertical scaling
		scaling.x = 1
		if (mouseEvent.shiftKey) then
			scaling.x = 1 / scaling.y
		end
	else
		if (not mouseEvent.shiftKey) then
			scaling.x = (scaling.x + scaling.y) / 2
			scaling.y = scaling.x
		end
	end

	local flip = false
	if (scaling.x * self.lastScaleX < -0.0001) then
		if (scaling.y * self.lastScaleY > 0.0001) then
			flip = true
		end
	elseif (scaling.y * self.lastScaleY < -0.0001) then
		if (scaling.x * self.lastScaleX > 0.0001) then
			flip = true
		end
	end
	mesh:ScalePoints(scaling.x, scaling.y, center, flip)
	--[[--]]
	--offset correction
	local k = 1
	if scaling.x * scaling.y < 0 then k = -1 end
	self:FixOffsets(moho, mesh, k)

	
	if (flip) then
		self.lastScaleX = scaling.x
		self.lastScaleY = scaling.y
	end
	
	--TODO:apply all final calculations here, to use onion skin (uncomment next line and call a universal method to fix delta)
	--moho:AddPointKeyframe(moho.drawingFrame)
	self:AddPointKeyframe(moho, moho.drawingFrame)

	if (self:UseFixedHandles(moho)) then
		mesh:PreserveHandlePositions()
	end

	mouseEvent.view:DrawMe()
end

function AE_TransformPoints:OnMouseMoved_R(moho, mouseEvent)
	if (self.numSel < 2) then
		return
	end

	local mesh = moho:DrawingMesh()
	if (mesh == nil) then
		return
	end

	local angle = self.startAngle
	local v1 = self.lastVec - self.centerVec
	local v2 = mouseEvent.drawingVec - self.centerVec
	v2:Rotate(-math.atan2(v1.y, v1.x))
	angle = angle + math.atan2(v2.y, v2.x)
	self.startAngle = angle
	if (mouseEvent.shiftKey) then
		angle = angle / (math.pi / 4)
		angle = (math.pi / 4) * LM.Round(angle)
	end

	mesh:RotatePoints(angle, self.centerVec)
	--TODO:apply all final calculations here, to use onion skin (uncomment next line and call a universal method to fix delta)
	--moho:AddPointKeyframe(moho.drawingFrame)
	self:AddPointKeyframe(moho, moho.drawingFrame)

	self.lastVec:Set(mouseEvent.drawingVec)

	if (self:UseFixedHandles(moho)) then
		mesh:PreserveHandlePositions()
	end

	mouseEvent.view:DrawMe()
end

function AE_TransformPoints:OnMouseUp(moho, mouseEvent)

	if (mouseEvent.altKey) and (mouseEvent.shiftKey) then
		return
	end	

	if (self.mode == self.selectMode) then
		mouseEvent.ctrlKey = false
		LM_SelectPoints:OnMouseUp(moho, mouseEvent)
		self.dragging = false
		self.selID = -1
		mouseEvent.view:DrawMe()
		return
	end

	local mesh = moho:DrawingMesh()
	if (mesh == nil) then
		return
	end

	-- if we're just moving a single point, then try to auto-weld it
	if (self.mode == 0 and self.selID >= 0 and self.numSel == 1 and self.autoWeld and not(mouseEvent.altKey) and moho.drawingLayer:CurrentAction() == "" and not(moho:DisableDrawingTools())) then
		if (mesh:Point(self.selID):IsEndpoint()) then
			if (not AE_TransformPoints:WeldPoints(moho, mouseEvent.view, self.dragging) and self.selID >= 0) then
				-- Failed to weld this endpoint to another point.
				-- Instead, try welding it to the middle of a nearby curve.
				local m = LM.Matrix:new_local()
				local v = LM.Vector2:new_local()
				local pt = LM.Point:new_local()
				local curveID = -1
				local segID = -1
				local pickWidth = 3

				moho.drawingLayer:GetFullTransform(moho.frame, m, moho.document)
				v:Set(mesh:Point(self.selID).fPos)
				m:Transform(v)
				mouseEvent.view:Graphics():WorldToScreen(v, pt)
				while (pickWidth < self.autoWeldRadius) do
					curveID, segID = mesh:Point(self.selID):GetEndpointEdge(curveID, segID)
					curveID, segID = moho.view:PickEdge(pt, curveID, segID, pickWidth)
					if (curveID >= 0) then -- add a point in the middle of some curve
						if ((not mesh:Curve(curveID):IsPointOnSegment(self.selID, segID)) and
							(not mesh:Curve(curveID):IsPointOnSegment(self.selID, segID - 1)) and
							(not mesh:Curve(curveID):IsPointOnSegment(self.selID, segID + 1))) then -- don't weld the point back on itself
							local curve = mesh:Curve(curveID)
							mesh:AddPoint(curve:ClosestPointOnSegment(segID, mesh:Point(self.selID).fPos, true, true), curveID, segID, moho.drawingLayerFrame)
							if (mesh:WeldPoints(self.selID, mesh:CountPoints() - 1, moho.drawingLayerFrame)) then
								moho:Click()
								if (self.autoFill or self.autoStroke) then
									self:CreateShape(moho, mesh, mesh:CountPoints() - 1, self.autoFill)
								end
							end
							break
						end
					end
					pickWidth = pickWidth + 3
				end
			end
		end
	end

	self.dragging = false
	if (self.mode == 10) then -- bezier handle
		AE_Curvature:OnMouseUp(moho, mouseEvent)
	else
	
		--TODO: move this into separated method, something like self:StoreFPoses() or 
		--self:SaveAddPointKeyframe(frame, layer, allSelectedKeys)
		--[[
		for i, pt in pairs(self.selList) do
			self.fPoses[i]:Set(pt.fPos)
			--print(i, ": ", pt.fPos.x, ", ", pt.fPos.y)
		end
		
		moho:AddPointKeyframe(moho.drawingFrame, nil, MOHO.MohoGlobals.EditMultipleKeys)
		
		for i, pt in pairs(self.selList) do
			--print(i, ": ", pt.fPos.x, ", ", pt.fPos.y)
			--print("saved: ", i, ": ", self.fPoses[i].x, ", ", self.fPoses[i].y)
			local delta = self.fPoses[i] - pt.fPos
			pt.fPos = self.fPoses[i] + delta
		end
		
		moho:AddPointKeyframe(moho.drawingFrame, nil, MOHO.MohoGlobals.EditMultipleKeys)		
		
		--]]
		self:AddPointKeyframe(moho, moho.drawingFrame, nil, MOHO.MohoGlobals.EditMultipleKeys)
		
		if (self:UseFixedHandles(moho)) then
			self:RepairFixedHandles(moho, mesh)
		end
		
		--TODO: end of area to replace
		
		
		--next line is commented out for smartbone-safe version too, but I do not remember the reason 		
		--moho:NewKeyframe(CHANNEL_POINT)
		moho:UpdateUI()
	
	


	end
	
	self.selID = -1

	if (mouseEvent.pt.x == mouseEvent.startPt.x and mouseEvent.pt.y == mouseEvent.startPt.y) then
	    --#34389 - allow shift key to pass to select points so extended selections are possible
		--mouseEvent.shiftKey = false
		mouseEvent.ctrlKey = false
		mouseEvent.altKey = false
		LM_SelectPoints:SingleClickSelect(moho, mouseEvent, false, true)
	end
end

function AE_TransformPoints:AddPointKeyframe(moho, frame, layer, allSelectedKeys)
	for i, pt in pairs(self.selList) do
		self.fPoses[i]:Set(pt.fPos)
		--print(i, ": ", pt.fPos.x, ", ", pt.fPos.y)
	end
	
	moho:AddPointKeyframe(frame, layer, allSelectedKeys)
	
	for i, pt in pairs(self.selList) do
		--print(i, ": ", pt.fPos.x, ", ", pt.fPos.y)
		--print("saved: ", i, ": ", self.fPoses[i].x, ", ", self.fPoses[i].y)
		local delta = self.fPoses[i] - pt.fPos
		pt.fPos = self.fPoses[i] + delta
	end
	
	moho:AddPointKeyframe(frame, layer, allSelectedKeys)		
	

end

function AE_TransformPoints:FixHandles(moho, mesh)
	self.bezierList = {}
	for c=0, mesh:CountCurves()-1 do
		local nextCurveArray = {}
		for p=0, mesh:Curve(c):CountPoints()-1 do
			local preVect = mesh:Curve(c):GetControlHandle(p, moho.drawingLayerFrame, true)
			local postVect = mesh:Curve(c):GetControlHandle(p, moho.drawingLayerFrame, false)
			local pointArray = {pre = preVect, post = postVect}
			nextCurveArray[p] = pointArray
		end 
		self.bezierList[c] = nextCurveArray
	end
end 

function AE_TransformPoints:RepairFixedHandles(moho, mesh)
	if not self.bezierList then return end 
	local precision = 0.0000001
	for k,v in pairs(self.bezierList) do
		local nextCurve = mesh:Curve(k)
		if not nextCurve then return end
		for n,p in pairs(v) do
			local point = nextCurve:Point(n)
			if not point then return end
			local preVect = nextCurve:GetControlHandle(n, moho.drawingLayerFrame, true)
			local postVect = nextCurve:GetControlHandle(n, moho.drawingLayerFrame, false)
			if ((p.pre - preVect):Mag()>precision) or ((p.post - postVect):Mag()>precision) then 
				--if handles changed lets try to fix it
				---- get angle and scale to apply 
				---- or sum action influences and subtract it from current keys
				-- (see curvature for details)
			end
		end
	end


end 

function AE_TransformPoints:OnKeyDown(moho, keyEvent)

	local mesh = moho:DrawingMesh()
	if (mesh == nil) then
		return
	end

	if keyEvent.key == "0" and keyEvent.ctrlKey then
		self.customCenter:Set(mesh:SelectedCenter())
		keyEvent.view:DrawMe()
		return
	end

	if (keyEvent.keyCode == LM.GUI.KEY_BIND) then
		local doit = false
		if (self.dragging) then
			if (self.numSel == 1) then
				doit = true
			end
		else
			if (moho:CountSelectedPoints() == 1) then
				doit = true
			end
		end
		if (doit) then
			-- try welding the selected point to the nearest other point
			if (not AE_TransformPoints:WeldPoints(moho, keyEvent.view, self.dragging)) then
				-- Failed to weld this endpoint to another point.
				-- Instead, try welding it to the middle of a nearby curve.
				local selID = -1
				for i = 0, mesh:CountPoints() - 1 do
					if (mesh:Point(i).fSelected) then
						selID = i
						break
					end
				end
				if (selID >= 0) then
					local m = LM.Matrix:new_local()
					local v = LM.Vector2:new_local()
					local pt = LM.Point:new_local()
					local curveID = -1
					local segID = -1
					local pickWidth = 3
	
					moho.drawingLayer:GetFullTransform(moho.frame, m, moho.document)
					v:Set(mesh:Point(selID).fPos)
					m:Transform(v)
					keyEvent.view:Graphics():WorldToScreen(v, pt)
					while (pickWidth < self.autoWeldRadius) do
						curveID, segID = mesh:Point(selID):GetEndpointEdge(curveID, segID)
						curveID, segID = moho.view:PickEdge(pt, curveID, segID, pickWidth)
						if (curveID >= 0) then -- add a point in the middle of some curve
							if ((not mesh:Curve(curveID):IsPointOnSegment(selID, segID))) then -- don't weld the point back on itself
								local curve = mesh:Curve(curveID)
								mesh:AddPoint(curve:ClosestPointOnSegment(segID, mesh:Point(selID).fPos, true, true), curveID, segID, moho.drawingLayerFrame)
								if (mesh:WeldPoints(selID, mesh:CountPoints() - 1, moho.drawingLayerFrame)) then
									moho:Click()
									if (self.autoFill or self.autoStroke) then
										self:CreateShape(moho, mesh, mesh:CountPoints() - 1, self.autoFill)
									end
								end
								break
							end
						end
						pickWidth = pickWidth + 3
					end
				end
			else
				self.selID = -1
				self.numSel = -1
				--mesh:SelectNone()
			end
			keyEvent.view:DrawMe()
		end
	elseif ((keyEvent.keyCode == LM.GUI.KEY_DELETE) or (keyEvent.keyCode == LM.GUI.KEY_BACKSPACE)) then
		if (not self.dragging) then
			moho.document:PrepUndo(moho.drawingLayer)
			moho.document:SetDirty()
			MOHO.DeleteSelectedPoints(mesh)
			keyEvent.view:DrawMe()
		end
	elseif (keyEvent.ctrlKey) then
		local inc = 1
		if (keyEvent.shiftKey) then
			inc = 10
		end

		local m = LM.Matrix:new_local()
		moho.drawingLayer:GetFullTransform(moho.frame, m, moho.document)

		local fakeME = {}
		fakeME.view = keyEvent.view
		fakeME.pt = LM.Point:new_local()
		fakeME.pt:Set(keyEvent.view:Graphics():Width() / 2, keyEvent.view:Graphics():Height() / 2)
		fakeME.startPt = LM.Point:new_local()
		fakeME.startPt:Set(fakeME.pt)
		fakeME.vec = keyEvent.view:Point2Vec(fakeME.pt, m)
		fakeME.drawingVec = keyEvent.view:Point2Vec(fakeME.pt, m)
		fakeME.drawingStartVec = keyEvent.view:Point2Vec(fakeME.pt, m)
		fakeME.shiftKey = false
		fakeME.ctrlKey = false
		fakeME.altKey = keyEvent.altKey
		fakeME.penPressure = 0
		fakeME.doubleClick = false
		fakeME.eraser = false

		self.keyMovement = true

		if (keyEvent.keyCode == LM.GUI.KEY_UP) then
			moho.document:PrepUndo(MOHO.Localize("/Scripts/Tool/TransformPoints/TransformPointsUndoAction=Transform Point"), moho.drawingLayer)
			self.selID = self:SelIDForNudge(moho, mesh)
			if (self.selID == -2) then
				self.selID = -1
				return
			end
			self:OnMouseDown(moho, fakeME)
			fakeME.pt.y = fakeME.pt.y - inc
			fakeME.vec = keyEvent.view:Point2Vec(fakeME.pt, m)
			fakeME.drawingVec = keyEvent.view:Point2Vec(fakeME.pt, m)
			self:OnMouseMoved(moho, fakeME)
			self:OnMouseUp(moho, fakeME)
		elseif (keyEvent.keyCode == LM.GUI.KEY_DOWN) then
			moho.document:PrepUndo(MOHO.Localize("/Scripts/Tool/TransformPoints/TransformPointsUndoAction=Transform Point"), moho.drawingLayer)
			self.selID = self:SelIDForNudge(moho, mesh)
			if (self.selID == -2) then
				self.selID = -1
				return
			end
			self:OnMouseDown(moho, fakeME)
			fakeME.pt.y = fakeME.pt.y + inc
			fakeME.vec = keyEvent.view:Point2Vec(fakeME.pt, m)
			fakeME.drawingVec = keyEvent.view:Point2Vec(fakeME.pt, m)
			self:OnMouseMoved(moho, fakeME)
			self:OnMouseUp(moho, fakeME)
		elseif (keyEvent.keyCode == LM.GUI.KEY_LEFT) then
			moho.document:PrepUndo(MOHO.Localize("/Scripts/Tool/TransformPoints/TransformPointsUndoAction=Transform Point"), moho.drawingLayer)
			self.selID = self:SelIDForNudge(moho, mesh)
			if (self.selID == -2) then
				self.selID = -1
				return
			end
			self:OnMouseDown(moho, fakeME)
			fakeME.pt.x = fakeME.pt.x - inc
			fakeME.vec = keyEvent.view:Point2Vec(fakeME.pt, m)
			fakeME.drawingVec = keyEvent.view:Point2Vec(fakeME.pt, m)
			self:OnMouseMoved(moho, fakeME)
			self:OnMouseUp(moho, fakeME)
		elseif (keyEvent.keyCode == LM.GUI.KEY_RIGHT) then
			moho.document:PrepUndo(MOHO.Localize("/Scripts/Tool/TransformPoints/TransformPointsUndoAction=Transform Point"), moho.drawingLayer)
			self.selID = self:SelIDForNudge(moho, mesh)
			if (self.selID == -2) then
				self.selID = -1
				return
			end
			self:OnMouseDown(moho, fakeME)
			fakeME.pt.x = fakeME.pt.x + inc
			fakeME.vec = keyEvent.view:Point2Vec(fakeME.pt, m)
			fakeME.drawingVec = keyEvent.view:Point2Vec(fakeME.pt, m)
			self:OnMouseMoved(moho, fakeME)
			self:OnMouseUp(moho, fakeME)
		end

		self.keyMovement = false
	end
end

function AE_TransformPoints:OnInputDeviceEvent(moho, deviceEvent)
	local mesh = moho:DrawingMesh()
	if (mesh == nil) then
		return false
	end

	if (moho:CountSelectedPoints() < 1) then
		return false
	end

	if (deviceEvent.inputData:GetString("DeviceType") == "Wacom Multitouch") then
		local mtState = deviceEvent.inputData:GetInt("MultitouchState")
		if (mtState == 1) then -- first finger down
			self.mtFingersTouching = 0 -- we'll get the correct number on the next event
			self.mtTranslate = LM.Vector2:new_local()
			self.mtStartVec = LM.Vector2:new_local()
			self.mtAccumTranslate = LM.Vector2:new_local()
			self.mtScale = 1.0
			self.mtStartScale = 1.0
			self.mtAccumScale = 1.0
			self.mtAngle = 0.0
			self.mtStartAngle = 0.0
			self.mtAccumAngle = 0.0

			moho.document:SetDirty()
			moho.document:PrepUndo(moho.drawingLayer)
			self.numSel = moho:CountSelectedPoints()
			mesh:PrepMovePoints()
			if (self:UseFixedHandles(moho)) then
				mesh:PrepFixedHandles()
			end
			self.centerVec = mesh:SelectedCenter()
			if self.centerMode then self.centerVec:Set(self.customCenter) end
			self.mtTranslate:Set(0.0, 0.0)
			self.mtScale = 1.0
			self.mtAngle = 0.0
		elseif (mtState == 3) then -- last finger up
			self.mtFingersTouching = 0
			moho:AddPointKeyframe(moho.drawingFrame, nil, MOHO.MohoGlobals.EditMultipleKeys)
			moho:NewKeyframe(CHANNEL_POINT)
			moho:UpdateUI()
		elseif (mtState == 2) then -- dragging
			local fingersTouching = deviceEvent.inputData:GetInt("FingersTouching")
			if (fingersTouching ~= self.mtFingersTouching) then
				self.mtAccumTranslate = self.mtAccumTranslate + self.mtTranslate
				self.mtAccumScale = self.mtAccumScale * self.mtScale
				self.mtAccumAngle = self.mtAccumAngle + self.mtAngle
				self.mtFingersTouching = fingersTouching
				self.mtStartVec = deviceEvent.inputData:GetVector2("MultitouchCenterVector")
				self.mtStartAngle = deviceEvent.inputData:GetFloat("MultitouchAngle")
				self.mtStartScale = deviceEvent.inputData:GetFloat("MultitouchScale")
			end
			
			if (deviceEvent.inputData:GetBool("DoubleTouch")) then
				if (fingersTouching == 2) then
					self:HandleMessage(moho, deviceEvent.view, self.RESET)
					if (frame ~= moho.frame) then
						moho:SetCurFrame(moho.frame) -- force a refresh when editing a key at a different frame
					end
					moho.document:DepthSort()
				end
				return true
			end

			self.mtTranslate = deviceEvent.inputData:GetVector2("MultitouchCenterVector") - self.mtStartVec
			if (deviceEvent.shiftKey) then
				if (math.abs(self.mtTranslate.x) > math.abs(self.mtTranslate.y)) then
					self.mtTranslate.y = 0
				else
					self.mtTranslate.x = 0
				end
			end
			self.mtScale = 1.0
			self.mtAngle = 0.0
			if (self.mtFingersTouching > 1) then
				self.mtAngle = deviceEvent.inputData:GetFloat("MultitouchAngle") - self.mtStartAngle
				self.mtScale = deviceEvent.inputData:GetFloat("MultitouchScale") / self.mtStartScale
			end
			mesh:TransformPoints(self.mtAccumTranslate + self.mtTranslate, self.mtAccumScale * self.mtScale, self.mtAccumScale * self.mtScale, self.mtAccumAngle + self.mtAngle, self.centerVec)
			if (self:UseFixedHandles(moho)) then
				mesh:PreserveHandlePositions()
			end
			
			moho:AddPointKeyframe(moho.drawingFrame)
		end

		return true
	end

	return false
end

function AE_TransformPoints:DrawMe(moho, view)
	if (moho:IsPlaying()) then
		return
	end
	if (self.mode == self.selectMode and self.dragging) then
		LM_SelectPoints:DrawMe(moho, view)
	elseif (self.fillingShape) then
		local g = view:Graphics()
		local matrix = LM.Matrix:new_local()

		moho.drawingLayer:GetFullTransform(moho.frame, matrix, moho.document)
		g:Push()
		g:ApplyMatrix(matrix)

		view:DrawPreviewShape()

		g:Pop()
	elseif (self.dragging and self.numSel == 1) then
		if (moho.drawingLayer:CurrentAction() ~= "" or moho:DisableDrawingTools()) then
			return -- welding in the middle of an action can lead to unexpected results
		end
		local g = view:Graphics()
		local matrix = LM.Matrix:new_local()

		moho.drawingLayer:GetFullTransform(moho.frame, matrix, moho.document)
		g:Push()
		g:ApplyMatrix(matrix)

		if (self.endWeldToPoint) then
			g:SetColor(0, 255, 0, 128)
		else
			g:SetColor(255, 0, 0, 128)
		end
		g:SetSmoothing(true)
		g:SetBezierTolerance(2)
		g:FillCircle(self.endWeldVec, moho:PixelToDoc(self.autoWeldRadius) / g:CurrentScale(false))
		g:SetSmoothing(false)
	
		g:Pop()
	elseif (not self.dragging) then
		local mesh = moho:DrawingMesh()
		if (mesh == nil) then
			return
		end
		
		if (self.centerVec == nil) then
			self.centerVec = LM.Vector2:new_local()
			self.centerVec:Set(mesh:SelectedCenter())
			if not self.customCenter then 
				self.customCenter = LM.Vector2:new_local()
				self.customCenter:Set(mesh:SelectedCenter())
			end
			if self.centerMode then self.centerVec:Set(self.customCenter) end
		end		
		
		self.numSel = moho:CountSelectedPoints()
		if (self.numSel >= 2) then
			local g = view:Graphics()
			local min = LM.Vector2:new_local()
			local max = LM.Vector2:new_local()
			local markerR = 4
			local matrix = LM.Matrix:new_local()
			local centerVec = LM.Vector2:new_local()
			local v = LM.Vector2:new_local()
			local vc1 = LM.ColorVector:new_local()
			local vc2 = LM.ColorVector:new_local()
	
			vc1:Set(MOHO.MohoGlobals.SelCol)
			vc2:Set(MOHO.MohoGlobals.BackCol)
			--vc1 = (vc1 * 3 + vc2 * 4) / 7
			vc1 = (vc1 + vc2) / 2
			local col = vc1:AsColorStruct()

			local bbox = self:SelectedMeshBounds(moho, moho.drawingLayer, mesh, moho.drawingFrame, view)
			min:Set(bbox.fMin.x, bbox.fMin.y)
			max:Set(bbox.fMax.x, bbox.fMax.y)

			moho.drawingLayer:GetFullTransform(moho.frame, matrix, moho.document)
			g:Push()
			g:ApplyMatrix(matrix)

			g:SetColor(col)
			g:SetPenWidth(1)
			g:SetSmoothing(true)
			g:DrawLine(min.x, min.y, max.x, min.y)
			g:DrawLine(min.x, max.y, max.x, max.y)
			g:DrawLine(min.x, min.y, min.x, max.y)
			g:DrawLine(max.x, min.y, max.x, max.y)

			g:SetPenWidth(1)
			local rotWidth = max.x - min.x
			if (max.y - min.y > rotWidth) then
				rotWidth = max.y - min.y
			end
			rotWidth = rotWidth * 0.1
			g:DrawLine(min.x - rotWidth, min.y - rotWidth, min.x - rotWidth, max.y + rotWidth)
			g:DrawLine(min.x - rotWidth, max.y + rotWidth, max.x + rotWidth, max.y + rotWidth)
			g:DrawLine(max.x + rotWidth, max.y + rotWidth, max.x + rotWidth, min.y - rotWidth)
			g:DrawLine(max.x + rotWidth, min.y - rotWidth, min.x - rotWidth, min.y - rotWidth)

			g:SetSmoothing(false)
			v:Set(min.x, min.y) g:FrameCirclePixelRadius(v, markerR)
			v:Set(min.x, max.y) g:FrameCirclePixelRadius(v, markerR)
			v:Set(max.x, min.y) g:FrameCirclePixelRadius(v, markerR)
			v:Set(max.x, max.y) g:FrameCirclePixelRadius(v, markerR)
			v:Set((min.x + max.x) / 2, min.y) g:FrameCirclePixelRadius(v, markerR)
			v:Set((min.x + max.x) / 2, max.y) g:FrameCirclePixelRadius(v, markerR)
			v:Set(min.x, (min.y + max.y) / 2) g:FrameCirclePixelRadius(v, markerR)
			v:Set(max.x, (min.y + max.y) / 2) g:FrameCirclePixelRadius(v, markerR)

			if (self.dragging) then
				centerVec:Set(self.centerVec)
			else
				centerVec:Set(self.centerVec)
				--centerVec = mesh:SelectedCenter()
			end
			g:SetColor(col)
			g:SetSmoothing(true)
			g:DrawLine(centerVec.x - 0.05, centerVec.y, centerVec.x + 0.05, centerVec.y)
			g:DrawLine(centerVec.x, centerVec.y - 0.05, centerVec.x, centerVec.y + 0.05)

			g:Pop()
		end
		
	end
	-- Draw bezier handles
	if (self.showHandles) then
		local mesh = moho:DrawingMesh()
		if (mesh == nil) then
			return
		end
		
		local g = view:Graphics()
		local layerMatrix = LM.Matrix:new_local()
		moho.drawingLayer:GetFullTransform(moho.frame, layerMatrix, moho.document)
		g:Push()
		g:ApplyMatrix(layerMatrix)
		g:SetSmoothing(true)
		
		local meshLayer = moho:LayerAsVector(moho.drawingLayer)
		local selectedOnly = false
		--[[if (self.isDragging) then
			if (self.numSel > 1) then
				selectedOnly = true
			end
		elseif (moho:CountSelectedPoints() > 1) then
			selectedOnly = true
		end]]
		if (moho:CountSelectedPoints() < 2) then
			meshLayer:DrawHandles(moho.document, g, selectedOnly)
		end
		
		g:Pop()
	end
end

function AE_TransformPoints:WeldPoints(moho, view, dragging)
	local mesh = moho:DrawingMesh()
	if (mesh == nil) then
		return false
	end
	if (moho.drawingLayer:CurrentAction() ~= "" or moho:DisableDrawingTools()) then
		return false -- welding in the middle of an action can lead to unexpected results
	end

	moho.document:SetDirty()
	-- try welding to the nearest point
	if (not dragging) then
		self.selID = -1
		self.numSel = 0
		for i = 0, mesh:CountPoints() - 1 do
			local pt = mesh:Point(i)
			if (pt.fSelected) then
				self.numSel = self.numSel + 1
				self.selID = i
				if (self.numSel > 1) then
					break
				end
			end
		end

		if (self.numSel > 1) then
			self.selID = -1
		end
		if (self.selID < 0) then
			return false
		end
		moho.document:PrepUndo(moho.drawingLayer)
	end

	if (self.selID < 0) then
		return false
	end

	local testVec1 = LM.Vector2:new_local()
	local testVec2 = LM.Vector2:new_local()

	testVec1:Set(mesh:Point(self.selID).fPos)
	local closestID = mesh:ClosestPoint(testVec1, self.selID)
	if (closestID < 0) then
		self.selID = -1
		return false
	end
	testVec2:Set(mesh:Point(closestID).fPos)

	if (mesh:Point(self.selID).fHidden or mesh:Point(closestID).fHidden) then
		return false
	end

	-- convert the two chosen points to pixel coordinates
	-- this is to make sure that they are actually close enough in screen space
	local p1 = LM.Point:new_local()
	local p2 = LM.Point:new_local()
	local m = LM.Matrix:new_local()
	moho.drawingLayer:GetFullTransform(moho.frame, m, moho.document)
	m:Transform(testVec1)
	m:Transform(testVec2)
	view:Graphics():WorldToScreen(testVec1, p1)
	view:Graphics():WorldToScreen(testVec2, p2)
	p1.x = p1.x - p2.x
	p1.y = p1.y - p2.y
	closest = (p1.x * p1.x) + (p1.y * p1.y)
	if (closest < self.autoWeldRadius * self.autoWeldRadius) then -- found one close enough to weld
		if (mesh:ArePointsAdjacent(self.selID, closestID)) then
			-- this avoids welding adjacent points, particularly the end point of a curve to the previous point
			return true
		end
		if (mesh:WeldPoints(self.selID, closestID, moho.drawingLayerFrame)) then
			moho:Click()
			if (self.selID < closestID) then
				closestID = closestID - 1
			end
			self.selID = -1 -- flag to end this interaction
			if (self.autoFill or self.autoStroke) then
				self:CreateShape(moho, mesh, closestID, self.autoFill)
			end
		end
	else
		return false
	end

	self.selID = -1
	return true
end

function AE_TransformPoints:CreateShape(moho, mesh, ptID, filled)
	mesh:Point(ptID).fSelected = true
	mesh:SelectConnected()
	self.fillingShape = true
	moho.view:DrawMe()
	local shapeID = moho:CreateShape(filled)
	self.fillingShape = false
	if (shapeID >= 0) then
		local shape = mesh:Shape(shapeID)
		shape.fSelected = true
		if (shape.fFillAllowed) then
			shape.fHasFill = self.autoFill
		end
		shape.fHasOutline = self.autoStroke
		moho:NewKeyframe(CHANNEL_FILL)
		moho:NewKeyframe(CHANNEL_LINE)
		moho:UpdateSelectedChannels()
	else
		mesh:SelectNone()
		mesh:Point(ptID).fSelected = true
	end
	return shapeID
end

function AE_TransformPoints:SelIDForNudge(moho, mesh)
	local id = moho:CountSelectedPoints()
	if (id < 1) then
		return -2
	elseif (id > 1) then
		return -1
	else
		for i = 0, mesh:CountPoints() - 1 do
			if (mesh:Point(i).fSelected) then
				return i
			end
		end
	end
end

function AE_TransformPoints:UseFixedHandles(moho)
	if (not MOHO.IsMohoPro()) then
		return false
	end
	if (self.showHandles and self.fixedHandles) then-- and moho.drawingFrame == 0) then
		return true
	else
		return false
	end
end

-- **************************************************
-- Tool options - create and respond to tool's UI
-- **************************************************

AE_TransformPoints.DUMMY = MOHO.MSG_BASE
AE_TransformPoints.CHANGE = MOHO.MSG_BASE + 1
AE_TransformPoints.MODIFY_S = MOHO.MSG_BASE + 2
AE_TransformPoints.MODIFY_R = MOHO.MSG_BASE + 3
AE_TransformPoints.RESET = MOHO.MSG_BASE + 4
AE_TransformPoints.AUTOWELD = MOHO.MSG_BASE + 5
AE_TransformPoints.AUTOFILL = MOHO.MSG_BASE + 6
AE_TransformPoints.AUTOSTROKE = MOHO.MSG_BASE + 7
AE_TransformPoints.SHOWHANDLES = MOHO.MSG_BASE + 8
AE_TransformPoints.FIXEDHANDLES = MOHO.MSG_BASE + 9
AE_TransformPoints.FLIP_H = MOHO.MSG_BASE + 10
AE_TransformPoints.FLIP_V = MOHO.MSG_BASE + 11
AE_TransformPoints.SELECTITEM = MOHO.MSG_BASE + 12
AE_TransformPoints.FLIPDIRECTION = MOHO.MSG_BASE + 13
AE_TransformPoints.CENTERPOINTS = MOHO.MSG_BASE + 14
AE_TransformPoints.CENTERMODE = MOHO.MSG_BASE + 15
AE_TransformPoints.autoWeld = false
AE_TransformPoints.autoWeldRadius = 12
AE_TransformPoints.autoFill = true
AE_TransformPoints.autoStroke = true
AE_TransformPoints.showHandles = false
AE_TransformPoints.fixedHandles = true
AE_TransformPoints.centerMode = true
AE_TransformPoints.centerVec = LM.Vector2:new_local()
AE_TransformPoints.customCenter = LM.Vector2:new_local()

function AE_TransformPoints:DoLayout(moho, layout)
	self.menu = LM.GUI.Menu(MOHO.Localize("/Scripts/Tool/TransformPoints/SelectGroup=Select Group"))

	self.popup = LM.GUI.PopupMenu(120, false)
	self.popup:SetMenu(self.menu)
	layout:AddChild(self.popup)

	self.resetBut = LM.GUI.Button(MOHO.Localize("/Scripts/Tool/TransformPoints/Reset=Reset"), self.RESET)
	layout:AddChild(self.resetBut)



	layout:AddChild(LM.GUI.StaticText(MOHO.Localize("/Scripts/Tool/TransformPoints/Position=Position")))

	self.textX = LM.GUI.TextControl(0, "00.000", self.CHANGE, LM.GUI.FIELD_FLOAT, MOHO.Localize("/Scripts/Tool/TransformPoints/X=X:"))
	self.textX:SetWheelInc(0.1)
	layout:AddChild(self.textX)

	self.textY = LM.GUI.TextControl(0, "00.000", self.CHANGE, LM.GUI.FIELD_FLOAT, MOHO.Localize("/Scripts/Tool/TransformPoints/Y=Y:"))
	self.textY:SetWheelInc(0.1)
	layout:AddChild(self.textY)

	layout:AddChild(LM.GUI.StaticText(MOHO.Localize("/Scripts/Tool/TransformLayer/Scale=Scale")))

	self.scaleX = LM.GUI.TextControl(0, "00.000", self.MODIFY_S, LM.GUI.FIELD_FLOAT, MOHO.Localize("/Scripts/Tool/TransformPoints/X=X:"))
	self.scaleX:SetWheelInc(0)
	layout:AddChild(self.scaleX)

	self.scaleY = LM.GUI.TextControl(0, "00.000", self.MODIFY_S, LM.GUI.FIELD_FLOAT, MOHO.Localize("/Scripts/Tool/TransformPoints/Y=Y:"))
	self.scaleY:SetWheelInc(0)
	layout:AddChild(self.scaleY)

	layout:AddChild(LM.GUI.StaticText(MOHO.Localize("/Scripts/Tool/TransformLayer/Angle=Angle:")))
	self.angle = LM.GUI.TextControl(0, "000.000", self.MODIFY_R, LM.GUI.FIELD_FLOAT)
	self.angle:SetWheelInc(0)
	layout:AddChild(self.angle)

	

	self.autoWeldCheck = LM.GUI.CheckBox(MOHO.Localize("/Scripts/Tool/TransformPoints/AutoWeld=Auto-weld"), self.AUTOWELD)
	layout:AddChild(self.autoWeldCheck)

	self.autoFillCheck = LM.GUI.CheckBox(MOHO.Localize("/Scripts/Tool/TransformPoints/AutoFill=Auto-fill"), self.AUTOFILL)
	layout:AddChild(self.autoFillCheck)

	self.autoStrokeCheck = LM.GUI.CheckBox(MOHO.Localize("/Scripts/Tool/TransformPoints/AutoStroke=Auto-stroke"), self.AUTOSTROKE)
	layout:AddChild(self.autoStrokeCheck)

	if (MOHO.IsMohoPro()) then
		self.showHandleCheck = LM.GUI.ImageButton("ScriptResources/show_handles", MOHO.Localize("/Scripts/Tool/TransformPoints/ShowBezierHandles=Show Bezier Handles"), true, self.SHOWHANDLES, true)
		layout:AddChild(self.showHandleCheck)

		self.fixedHandleCheck = LM.GUI.ImageButton("ScriptResources/fixed_handles", MOHO.Localize("/Scripts/Tool/TransformPoints/FixedBezierHandles=Fixed Bezier Handles"), true, self.FIXEDHANDLES, true)
		layout:AddChild(self.fixedHandleCheck)
	end
	
	--self.centerPoints = LM.GUI.Button(MOHO.Localize("/Scripts/Tool/TransformPoints/Center=Center"), self.CENTERPOINTS)
	--layout:AddChild(self.centerPoints)
	
	self.centerPointsCheck = LM.GUI.ImageButton("channel_tracking_point_sel", "Use custom center (alt-click this button to center, alt-shift click screen to set custom center", true, self.CENTERMODE, true)
	layout:AddChild(self.centerPointsCheck)
	self.centerPointsCheck:SetAlternateMessage(self.CENTERPOINTS)

	self.flipH = LM.GUI.ImageButton("ScriptResources/flip_points_h", MOHO.Localize("/Scripts/Tool/SelectPoints/FlipH=Flip Horizontally"), false, self.FLIP_H, true)
	layout:AddChild(self.flipH)

	self.flipV = LM.GUI.ImageButton("ScriptResources/flip_points_v", MOHO.Localize("/Scripts/Tool/SelectPoints/FlipV=Flip Vertically"), false, self.FLIP_V, true)
	layout:AddChild(self.flipV)
	
	self.flipDirectionCB = LM.GUI.CheckBox("Flip direction", self.FLIPDIRECTION)
	layout:AddChild(self.flipDirectionCB)
	
	self.curNumber = LM.GUI.DynamicText("000")
	layout:AddChild(self.curNumber)
	

	
end

-- self.centerVec:Set(mesh:SelectedCenter())

function AE_TransformPoints:UpdateWidgets(moho)
	if (moho:CurrentTool() ~= "AE_TransformPoints") then
		return -- this could get called when doing a double-tap on a multitouch Wacom device with a different active tool
	end

	local mesh = moho:DrawingMesh()
	if (mesh == nil) then
		return
	end

	MOHO.BuildGroupMenu(self.menu, mesh, self.SELECTITEM, self.DUMMY)

	local center = mesh:SelectedCenter()
	self.centerPointsCheck:SetValue(self.centerMode)
	self.textX:SetValue(center.x)
	self.textY:SetValue(center.y)

	self.scaleX:SetValue(1)
	self.scaleY:SetValue(1)

	self.angle:SetValue(0)

	self.autoWeldCheck:SetValue(self.autoWeld)
	self.autoFillCheck:SetValue(self.autoFill)
	self.autoStrokeCheck:SetValue(self.autoStroke)
	if (MOHO.IsMohoPro()) then
		self.showHandleCheck:SetValue(self.showHandles)
		self.fixedHandleCheck:SetValue(self.fixedHandles)
		self.fixedHandleCheck:Enable(self.showHandles)
	end

	self.resetBut:Enable(moho.drawingFrame > 0)

	if (moho:CountSelectedPoints() > 1) then
		self.flipH:Enable(true)
		self.flipV:Enable(true)
	else
		self.flipH:Enable(false)
		self.flipV:Enable(false)
	end
	
	self.flipDirectionCB:SetValue(AE_Curvature.flipDirection)
	
	local selectedID = -1
	for i=0, mesh:CountPoints()-1 do 
		if mesh:Point(i).fSelected then
			selectedID = i
			break
		end
	end
	if selectedID > -1 then self.curNumber:SetValue(string.format("%s",selectedID)) end

end

function AE_TransformPoints:HandleMessage(moho, view, msg)
	local mesh = moho:DrawingMesh()
	if (mesh == nil) then
		return
	end

	if (msg == self.CHANGE) then
		moho.document:PrepUndo(moho.drawingLayer)
		moho.document:SetDirty()

		local offset = mesh:SelectedCenter()
		offset.x = self.textX:FloatValue() - offset.x
		offset.y = self.textY:FloatValue() - offset.y

		mesh:PrepMovePoints()
		if (self:UseFixedHandles(moho)) then
			mesh:PrepFixedHandles()
		end
		mesh:TranslatePoints(offset)
		moho:AddPointKeyframe(moho.drawingFrame, nil, MOHO.MohoGlobals.EditMultipleKeys)
		if (self:UseFixedHandles(moho)) then
			mesh:PreserveHandlePositions()
		end
		moho:NewKeyframe(CHANNEL_POINT)
		moho:UpdateUI()
		
	elseif (msg == self.CENTERPOINTS) then
		self.centerVec:Set(mesh:SelectedCenter())
	elseif (msg == self.CENTERMODE) then
		self.centerMode = self.centerPointsCheck:Value()
		if self.centerMode then self.centerVec:Set(self.customCenter) else self.centerVec:Set(mesh:SelectedCenter()) end

	elseif (msg == self.RESET) then
		if (moho.drawingFrame > 0) then
			moho.document:PrepUndo(moho.drawingLayer)
			moho.document:SetDirty()

			for i = 0, mesh:CountPoints() - 1 do
				local pt = mesh:Point(i)
				if (pt.fSelected) then
					pt:SetPos(pt.fAnimPos:GetValue(0), moho.drawingLayerFrame)
				end
			end
		end
		self:UpdateWidgets(moho)
		moho:NewKeyframe(CHANNEL_POINT)
		moho:UpdateUI()
	elseif (msg == self.MODIFY_S) then
		moho.document:PrepUndo(moho.drawingLayer)
		moho.document:SetDirty()
		
		self:PopulateSelList(moho, mesh)
		
		local centerVec = mesh:SelectedCenter()
		local scaleX = self.scaleX:FloatValue()
		local scaleY = self.scaleY:FloatValue()

		self.scaleX:SetValue(1)
		self.scaleY:SetValue(1)
		mesh:PrepMovePoints()
		if (self:UseFixedHandles(moho)) then
			mesh:PrepFixedHandles()
		end
		local flip = false
		if (scaleX < 0.001) then
			if (scaleY > 0.001) then
				flip = true
			end
		elseif (scaleY < 0.001) then
			if (scaleX > 0.001) then
				flip = true
			end
		end
		mesh:ScalePoints(scaleX, scaleY, centerVec, flip)
		

		local k = 1
		if flip then k = -1 end
		self:FixOffsets(moho, mesh, k)	
		
		moho:AddPointKeyframe(moho.drawingFrame, nil, MOHO.MohoGlobals.EditMultipleKeys)
		if (self:UseFixedHandles(moho)) then
			mesh:PreserveHandlePositions()
		end
			

		
		moho:NewKeyframe(CHANNEL_POINT)
		moho:UpdateUI()
	elseif (msg == self.MODIFY_R) then
		moho.document:PrepUndo(moho.drawingLayer)
		moho.document:SetDirty()

		local centerVec = mesh:SelectedCenter()
		local angle = math.rad(self.angle:FloatValue())

		self.angle:SetValue(0)
		mesh:PrepMovePoints()
		if (self:UseFixedHandles(moho)) then
			mesh:PrepFixedHandles()
		end
		mesh:RotatePoints(angle, centerVec)
		moho:AddPointKeyframe(moho.drawingFrame, nil, MOHO.MohoGlobals.EditMultipleKeys)
		if (self:UseFixedHandles(moho)) then
			mesh:PreserveHandlePositions()
		end
		moho:NewKeyframe(CHANNEL_POINT)
		moho:UpdateUI()
	elseif (msg == self.AUTOWELD) then
		self.autoWeld = self.autoWeldCheck:Value()
	elseif (msg == self.AUTOFILL) then
		self.autoFill = self.autoFillCheck:Value()
	elseif (msg == self.AUTOSTROKE) then
		self.autoStroke = self.autoStrokeCheck:Value()
	elseif (msg == self.SHOWHANDLES) then
		if (MOHO.IsMohoPro()) then
			self.showHandles = self.showHandleCheck:Value()
			moho:UpdateUI()
		end
	elseif (msg == self.FIXEDHANDLES) then
		if (MOHO.IsMohoPro()) then
			self.fixedHandles = self.fixedHandleCheck:Value()
		end
	elseif (msg == self.FLIP_H) then
		moho.document:PrepUndo(moho.drawingLayer)
		moho.document:SetDirty()
		
		self:PopulateSelList(moho, mesh)
	
		local center = mesh:SelectedCenter()
	
		for i = 0, mesh:CountPoints() - 1 do
			local pt = mesh:Point(i)
			if (pt.fSelected) then
				local f = center.x - pt.fPos.x
				pt.fPos.x = center.x + f
				--pt:FlipControlHandles(moho.drawingFrame)
				self:FlipControlHandles(moho, moho.layer, mesh, pt, moho.drawingFrame)
			end
		end
	
		--moho:AddPointKeyframe(moho.drawingFrame)
		self:AddPointKeyframe(moho, moho.drawingFrame)
		moho:NewKeyframe(CHANNEL_POINT)
		moho:UpdateUI()
	elseif (msg == self.FLIP_V) then
		moho.document:PrepUndo(moho.drawingLayer)
		moho.document:SetDirty()
		
		self:PopulateSelList(moho, mesh)
	
		local center = mesh:SelectedCenter()
	
		for i = 0, mesh:CountPoints() - 1 do
			local pt = mesh:Point(i)
			if (pt.fSelected) then
				local f = center.y - pt.fPos.y
				pt.fPos.y = center.y + f
				--pt:FlipControlHandles(moho.drawingFrame)
				self:FlipControlHandles(moho, moho.layer, mesh, pt, moho.drawingFrame)
			end
		end
	
		--moho:AddPointKeyframe(moho.drawingFrame)
		self:AddPointKeyframe(moho, moho.drawingFrame)
		moho:NewKeyframe(CHANNEL_POINT)
		moho:UpdateUI()
	elseif (msg == self.SELECTITEM) then
		mesh:SelectNone()
		local i = msg - self.SELECTITEM
		local name = mesh:Group(i):Name()
		mesh:SelectGroup(name)
		moho:UpdateUI()
	elseif (msg == self.FLIPDIRECTION) then
		AE_Curvature.flipDirection = self.flipDirectionCB:Value()
	end
end

function AE_TransformPoints:FlipControlHandles(moho, layer, mesh, point, frame)
	local skel = layer:ControllingSkeleton()
	for c = 0, point:CountCurves() - 1 do
		local curve, where = point:Curve(c)

		local inChannel = AE_Utilities:GetOffsetChannel(moho, layer, curve, where, true)
		local outChannel = AE_Utilities:GetOffsetChannel(moho, layer, curve, where, false)
		local inCurVal = inChannel:GetValue(frame)
		local outCurVal = outChannel:GetValue(frame)
		local inDelta = 0
		local outDelta = 0
		if skel then
			inDelta = AE_Utilities:SumActionInfluences(moho, frame, inChannel, layer)
			outDelta = AE_Utilities:SumActionInfluences(moho, frame, outChannel, layer)
		end
		local desiredInVal = -(inCurVal + inDelta)
		local desiredOutVal = -(outCurVal + outDelta)
		local targetInVal = desiredInVal - inDelta
		local targetOutVal = desiredOutVal - outDelta
		inChannel:SetValue(frame, targetInVal)
		outChannel:SetValue(frame, targetOutVal)
	end
end

function AE_TransformPoints:FixOffsets(moho, mesh, k)
	for i, pt in pairs(self.selList) do
		for c = 0, pt:CountCurves() - 1 do
			local curve, where = pt:Curve(c)

			local inChannel = AE_Utilities:GetOffsetChannel(moho, moho.layer, curve, where, true)
			local outChannel = AE_Utilities:GetOffsetChannel(moho, moho.layer, curve, where, false)	
			inDelta = AE_Utilities:SumActionInfluences(moho, moho.drawingFrame, inChannel, moho.layer)
			outDelta = AE_Utilities:SumActionInfluences(moho, moho.drawingFrame, outChannel, moho.layer)
			if math.abs(inDelta) > 0.01 then
				local wanted = (self.inOffsets[i][c] + inDelta) * k
				local newOffset = wanted - inDelta
				curve:SetOffset(where, newOffset, moho.drawingFrame, true)
			end
			if math.abs(outDelta) > 0.01 then
				local wanted = (self.outOffsets[i][c] + outDelta) * k
				local newOffset = wanted - outDelta
				curve:SetOffset(where, newOffset, moho.drawingFrame, false)
			end			
		end
	end
end

Icon
AE Transform points and curvature
Listed

Script type: Tool

Uploaded: Feb 12 2021, 21:53

Last modified: Feb 22 2021, 05:25

Tweak for built-in transform points and curvature to fix errors on smartbones driven points and bezier handles
This script, and all other scripts on this site are distributed as free software under the GNU General Public License 3.0 or later.
Downloads count: 22