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

ScriptName = "LK_TransformPoints"

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

LK_TransformPoints = {}

LK_TransformPoints.BASE_STR = 2375

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

function LK_TransformPoints:Version()
	return "1.1 based on 10.0"
end

function LK_TransformPoints:IsBeginnerScript()
	return true
end

function LK_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 LK_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 LK_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 LK_TransformPoints:Creator()
	return "Lost Marble LLC Lukas mod"
end

function LK_TransformPoints:ColorizeIcon()
	return true
end

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

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

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

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

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

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

LK_TransformPoints.dragging = false
LK_TransformPoints.keyMovement = false
LK_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, 11:pivot offset, 100:select points
LK_TransformPoints.numSel = 0
LK_TransformPoints.selID = -1
LK_TransformPoints.fillingShape = false
LK_TransformPoints.endWeldVec = LM.Vector2:new_local()
LK_TransformPoints.endWeldVec:Set(-10000000, -10000000)
LK_TransformPoints.endWeldToPoint = true
LK_TransformPoints.startAngle = 0
LK_TransformPoints.lastVec = LM.Vector2:new_local()
LK_TransformPoints.lastSelectedCount = 0
LK_TransformPoints.pivotOffset = LM.Vector2:new_local()
LK_TransformPoints.selectMode = 100
LK_TransformPoints.markerR = 8

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

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

function LK_TransformPoints:IsRelevant(moho)
	if MohoMode ~= nil then
		if MohoMode.vanilla then
			return false
		end
	end
	local mesh = moho:DrawingMesh()
	if (mesh == nil) then
		return false
	end
	return true
end

function LK_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 LK_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 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) < self.markerR and math.abs(pt.y - mouseEvent.pt.y) < self.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) < self.markerR and math.abs(pt.y - mouseEvent.pt.y) < self.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) < self.markerR and math.abs(pt.y - mouseEvent.pt.y) < self.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) < self.markerR and math.abs(pt.y - mouseEvent.pt.y) < self.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) < self.markerR and math.abs(pt.y - mouseEvent.pt.y) < self.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) < self.markerR and math.abs(pt.y - mouseEvent.pt.y) < self.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) < self.markerR and math.abs(pt.y - mouseEvent.pt.y) < self.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) < self.markerR and math.abs(pt.y - mouseEvent.pt.y) < self.markerR) then
		-- top
		return 8
	end

	-- test for pivot point
	if (self.dragging) then
		v:Set(self.centerVec)
	else
		v = mesh:SelectedCenter()
		v = v + self.pivotOffset
	end
	m:Transform(v)
	mouseEvent.view:Graphics():WorldToScreen(v, pt)
	if (math.abs(pt.x - mouseEvent.pt.x) < self.markerR and math.abs(pt.y - mouseEvent.pt.y) < self.markerR) then
		-- pivot point
		return 11
	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 LK_TransformPoints:OnMouseDown(moho, mouseEvent)
	local mesh = moho:DrawingMesh()
	if (mesh == nil) then
		return
	end
	self.dragging = true

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

	self.centerVec = mesh:SelectedCenter()
	self.centerVec = self.centerVec + self.pivotOffset
	self.startPivotOffset = LM.Vector2:new_local()
	self.startPivotOffset:Set(self.pivotOffset)

	self.mode = self:TestMousePoint(moho, mouseEvent)
	if (mouseEvent.ctrlKey) then
		self.mode = self.selectMode
		mouseEvent.ctrlKey = false
		LM_SelectPoints:OnMouseDown(moho, mouseEvent)
		return
	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
			LM_Curvature:TestForClosestHandle(moho, mouseEvent)
			if (LM_Curvature.selID >= 0 and LM_Curvature.handleSide ~= 0) then -- a bezier handle was selected
				self.mode = 10
				local point = mesh:Point(LM_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, LM_Curvature.handleSide == -1)
					local offset = curve:GetOffset(ptPos, moho.drawingFrame, LM_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
		if (self.mode ~= 11) then
			moho.document:PrepUndo(moho.drawingLayer)
		end
	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
		LM_Curvature:OnMouseDown(moho, mouseEvent)
		return
	elseif (self.mode == 11) then -- pivot point
		self.numSel = moho:CountSelectedPoints(true)
		self.lastVec:Set(mouseEvent.drawingVec)
	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()
	end

	mouseEvent.view:DrawMe()
end

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

	if (not self.dragging) then
		self.centerVec = mesh:SelectedCenter()

		local mode = self:TestMousePoint(moho, mouseEvent)

		if (mode == 0) then
			mouseEvent.view:SetCursor(MOHO.moveCursor)
		elseif (mode == 1) then
			mouseEvent.view:SetCursor(MOHO.rotateCursor)
		elseif (mode == 11) then
			mouseEvent.view:SetCursor(MOHO.crosshairCursor)
		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
		LM_Curvature:OnMouseMoved(moho, mouseEvent)
		return
	elseif (self.mode == 11) then -- pivot
		self:OnMouseMoved_P(moho, mouseEvent)
	else -- scale
		self:OnMouseMoved_S(moho, mouseEvent)
	end
end

function LK_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

	moho:AddPointKeyframe(moho.drawingFrame)

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

	mouseEvent.view:DrawMe()
end

function LK_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)

	if (mouseEvent.altKey) then
		local v = mesh:SelectedCenter()
		self.pivotOffset = self.centerVec - v
	else
		local v = self.centerVec - center
		v.x = v.x * scaling.x
		v.y = v.y * scaling.y
		v = v + center
		self.pivotOffset = v - mesh:SelectedCenter()
	end

	if (flip) then
		self.lastScaleX = scaling.x
		self.lastScaleY = scaling.y
	end
	moho:AddPointKeyframe(moho.drawingFrame)

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

	mouseEvent.view:DrawMe()
end

function LK_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)
	moho:AddPointKeyframe(moho.drawingFrame)

	self.lastVec:Set(mouseEvent.drawingVec)

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

	mouseEvent.view:DrawMe()
end

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

	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

	self.pivotOffset = self.startPivotOffset + curVec
	self.centerVec = mesh:SelectedCenter()
	self.centerVec = self.centerVec + self.pivotOffset

	mouseEvent.view:DrawMe()
end

function LK_TransformPoints:OnMouseUp(moho, mouseEvent)
	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 LK_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) and (not mesh:ContinuousTriangulation())) 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
		LM_Curvature:OnMouseUp(moho, mouseEvent)
	elseif (self.mode ~= 11) then
		moho:AddPointKeyframe(moho.drawingFrame, nil, MOHO.MohoGlobals.EditMultipleKeys)
		moho:NewKeyframe(CHANNEL_POINT)
		moho:UpdateUI()

		if (self.mode == 0) then
			-- translate : don't modify the pivot
		elseif (self.mode == 1) then
			-- rotate : move the pivot
			local v = mesh:SelectedCenter()
			self.pivotOffset = self.centerVec - v
		end
	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 LK_TransformPoints:OnKeyDown(moho, keyEvent)
	local mesh = moho:DrawingMesh()
	if (mesh == nil) then
		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 LK_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) and (not mesh:ContinuousTriangulation())) 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
	moho:UpdateUI()
end

function LK_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()
			self.centerVec = self.centerVec + self.pivotOffset
			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 LK_TransformPoints:DrawMe(moho, view)
	if (moho:IsPlaying()) then
		return
	end
	local g = view:Graphics()
	local layerMatrix = LM.Matrix:new_local()
	moho.layer:GetFullTransform(moho.frame, layerMatrix, moho.document)
	g:Push()
	g:ApplyMatrix(layerMatrix)
	if (self.lastSelectedCount ~= moho:CountSelectedPoints()) then
		self.lastSelectedCount = moho:CountSelectedPoints()
		self.pivotOffset:Set(0, 0)
	end
	if (self.mode == self.selectMode and self.dragging) then
		LM_SelectPoints:DrawMe(moho, view)
	elseif (self.fillingShape) then
		local matrix = LM.Matrix:new_local()
		moho.drawingLayer:GetFullTransform(moho.frame, matrix, moho.document)
	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
		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)
	elseif (not self.dragging or self.mode == 11) then
		local mesh = moho:DrawingMesh()
		if (mesh == nil) then
			return
		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 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)

			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:SetPenWidth(2)
			v:Set(min.x, min.y) g:FrameCirclePixelRadius(v, self.markerR)
			v:Set(min.x, max.y) g:FrameCirclePixelRadius(v, self.markerR)
			v:Set(max.x, min.y) g:FrameCirclePixelRadius(v, self.markerR)
			v:Set(max.x, max.y) g:FrameCirclePixelRadius(v, self.markerR)
			v:Set((min.x + max.x) / 2, min.y) g:FrameCirclePixelRadius(v, self.markerR)
			v:Set((min.x + max.x) / 2, max.y) g:FrameCirclePixelRadius(v, self.markerR)
			v:Set(min.x, (min.y + max.y) / 2) g:FrameCirclePixelRadius(v, self.markerR)
			v:Set(max.x, (min.y + max.y) / 2) g:FrameCirclePixelRadius(v, self.markerR)
			g:SetPenWidth(1)

			if (self.dragging) then
				centerVec:Set(self.centerVec)
			else
				centerVec = mesh:SelectedCenter()
				centerVec = centerVec + self.pivotOffset
			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)
			v:Set(centerVec.x, centerVec.y) g:FrameCirclePixelRadius(v, self.markerR)
		end
	end
	-- * Lukas mod draw bezier handles:
	if self.showHandles then
		local mesh = moho:DrawingMesh()
		if mesh == nil then
			return
		end
		local g = view:Graphics()
		g:SetSmoothing(true)
		local meshLayer = moho:LayerAsVector(moho.drawingLayer)
		local selectedOnly = false -- * Currently always false
		-- * Curves:
		for i=0, mesh:CountCurves()-1 do
	        local curve = mesh:Curve(i)
	        local vec2 = LM.Vector2:new_local()
	        vec2:Set(0,0)
	        -- * Point Handles:
			for i = 0, curve:CountPoints()-1 do
				local point = curve:Point(i)
				local firstPoint = false
				local lastPoint = false
				if i == 0 and point:IsEndpoint() then
					firstPoint = true
				elseif point:IsEndpoint() then
					lastPoint = true
				end
				--
				if not point.fHidden and (not selectedOnly or point.fSelected) then
					local ptPos = point.fPos
					local handlePos = curve:GetControlHandle(i, moho.frame, true)
					local handlePos2 = curve:GetControlHandle(i, moho.frame, false)
					-- * Lines to handles:
					g:SetColor(63, 77, 76) -- * Handle color preference is not available, there's no MOHO.MohoGlobals.HandleCol
					if not firstPoint then
						g:DrawLine(ptPos.x, ptPos.y, handlePos.x, handlePos.y)
					end
					if not lastPoint then
						g:DrawLine(ptPos.x, ptPos.y, handlePos2.x, handlePos2.y)
					end
					-- * Handles:
					local handleRadius = 3
					if point.fSelected then
						g:SetColor(MOHO.MohoGlobals.SelCol)
					end
					if not firstPoint then
						g:FillCirclePixelRadius(handlePos, handleRadius)
					end
					if not lastPoint then
						g:FillCirclePixelRadius(handlePos2, handleRadius)
					end
					if not point.fSelected or self.lockedMode then
						g:SetColor(MOHO.MohoGlobals.ElemCol)
					end
					if point.fSelected and not self.lockedMode then
						g:SetColor(MOHO.MohoGlobals.SelCol)
					end
					g:DrawFatMarker(ptPos.x, ptPos.y, 3) -- * Default point.
				end
			end
			-- *
		end
	end
	-- * end bezier mod
	-- * start Lukas mod color points:
	local mesh = moho:DrawingMesh()
	if MOHO.hasbit(view:QualityFlags(), MOHO.bit(MOHO.LDQ_WIREFRAME)) then
		if mesh ~= nil then
			for i = 0, mesh:CountGroups()-1 do
				local group = mesh:Group(i)
				for j = 0, group:CountPoints()-1 do
					local groupName = group:Name()
					if groupName ~= "Shy" then
						local color = 12
						for c = 1, #FO_Utilities.colorNames do
							local colorName = FO_Utilities.colorNames[c]
							if string.lower(groupName) == string.lower(colorName) then
								color = c
							end
						end
						local point = group:Point(j)
						if not point.fHidden then
							if point.fSelected then
								g:SetColor(MOHO.MohoGlobals.SelCol)
								g:DrawFatMarker(point.fPos.x, point.fPos.y, 4)
							else
								g:SetColor(MOHO.MohoGlobals.ElemCol)
								g:DrawFatMarker(point.fPos.x, point.fPos.y, 3)
							end
							g:SetColor(FO_Utilities.colorsR[color], FO_Utilities.colorsG[color], FO_Utilities.colorsB[color])
							g:DrawFatMarker(point.fPos.x, point.fPos.y, 2)
						end
					end
				end
			end
		end
	end
	-- * end Lukas mod color points
	g:Pop()
end

function LK_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) and (not mesh:ContinuousTriangulation())) then
				self:CreateShape(moho, mesh, closestID, self.autoFill)
			end
		end
	else
		return false
	end

	self.selID = -1
	return true
end

function LK_TransformPoints:CreateShape(moho, mesh, ptID, filled)
	mesh:Point(ptID).fSelected = true
	mesh:SelectConnected()
	self.fillingShape = true
	moho.view:DrawMe()
	local shapeID = moho:CreateShape(filled, false, 0)
	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 LK_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 LK_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
-- **************************************************

LK_TransformPoints.DUMMY = MOHO.MSG_BASE
LK_TransformPoints.CHANGE = MOHO.MSG_BASE + 1
LK_TransformPoints.MODIFY_S = MOHO.MSG_BASE + 2
LK_TransformPoints.MODIFY_R = MOHO.MSG_BASE + 3
LK_TransformPoints.RESET = MOHO.MSG_BASE + 4
LK_TransformPoints.AUTOWELD = MOHO.MSG_BASE + 5
LK_TransformPoints.AUTOFILL = MOHO.MSG_BASE + 6
LK_TransformPoints.AUTOSTROKE = MOHO.MSG_BASE + 7
LK_TransformPoints.SHOWHANDLES = MOHO.MSG_BASE + 8
LK_TransformPoints.FIXEDHANDLES = MOHO.MSG_BASE + 9
LK_TransformPoints.FLIP_H = MOHO.MSG_BASE + 10
LK_TransformPoints.FLIP_V = MOHO.MSG_BASE + 11
LK_TransformPoints.SELECTITEM = MOHO.MSG_BASE + 12
LK_TransformPoints.autoWeld = false
LK_TransformPoints.autoWeldRadius = 12
LK_TransformPoints.autoFill = false
LK_TransformPoints.autoStroke = false
LK_TransformPoints.showHandles = false
LK_TransformPoints.fixedHandles = true

function LK_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.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)
end

function LK_TransformPoints:UpdateWidgets(moho)
	if (moho:CurrentTool() ~= "LK_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.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.autoWeldCheck:Enable(not mesh:ContinuousTriangulation())
	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
end

function LK_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.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()

		local centerVec = mesh:SelectedCenter()
		centerVec = centerVec + self.pivotOffset
		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 v = mesh:SelectedCenter()
		self.pivotOffset = centerVec - v
		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()
		centerVec = centerVec + self.pivotOffset
		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)
		local v = mesh:SelectedCenter()
		self.pivotOffset = centerVec - v
		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()
	
		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)
			end
		end
	
		moho:AddPointKeyframe(moho.drawingFrame)
		moho:NewKeyframe(CHANNEL_POINT)
		moho:UpdateUI()
	elseif (msg == self.FLIP_V) then
		moho.document:PrepUndo(moho.drawingLayer)
		moho.document:SetDirty()
	
		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)
			end
		end
	
		moho:AddPointKeyframe(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()
	end
end

Icon
Transform Points
Unlisted

Author: Lukas View Script
Script type: Tool

Uploaded: Jun 02 2022, 13:54

Last modified: Jun 07 2022, 03:07

Transform Points mod with custom bezier looks and color points
If you're using LK_SelectPoints and LK_Curvature, you probably also want the transform tool to look the same. This is the standard tool, with only some viewport tweaks.
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: 230