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

ScriptName = "LK_Curvature"

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

LK_Curvature = {}

LK_Curvature.BASE_STR = 2070

function LK_Curvature:Name()
	return "Curvature"
end

function LK_Curvature:Version()
	return "2.5 based on 6.0"
end

function LK_Curvature:Description()
	return MOHO.Localize("/Scripts/Tool/Curvature/Description=Drag side to side to adjust the curvature through the selected points (hold <ctrl/cmd> to select points, <alt> to maintain peaked points, <shift> to constrain handle direction)")
end

function LK_Curvature:Creator()
	return "Smith Micro Software, Inc. Modded by Wes, Modded by Lukas Krepel, Frame Order"
end

function LK_Curvature:UILabel()
	return(MOHO.Localize("/Scripts/Tool/Curvature/Curvature=Curvature"))
end

function LK_Curvature:LoadPrefs(prefs)
	self.showHandles = prefs:GetBool("LM_TransformPoints.showHandles", false)
	self.lockedMode = prefs:GetBool("LK_Curvature.lockedMode", false)
end

function LK_Curvature:SavePrefs(prefs)
	prefs:SetBool("LM_TransformPoints.showHandles", self.showHandles)
	prefs:SetBool("LK_Curvature.lockedMode", self.lockedMode)
end

function LK_Curvature:ResetPrefs()
	LM_TransformPoints.showHandles = false
end

function LK_Curvature:ColorizeIcon()
	return true
end

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

LK_Curvature.numSel = 0
LK_Curvature.selID = -1
LK_Curvature.savedVal = 0
LK_Curvature.MIN_CURVATURE = MOHO.PEAKED -- The built-in minimum for a long time was MOHO.PEAKED
LK_Curvature.MAX_CURVATURE = 1.0 -- The built-in maximum for a long time was 2 / 3
LK_Curvature.selectMode = false
LK_Curvature.lockedMode = true

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

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

-- * Modded by Lukas to incorporate MohoMode:
function LK_Curvature:IsRelevant(moho)
	local relevance = true
	local mesh = moho:DrawingMesh()
	if (mesh == nil) then
		relevance = false
	end
	if MohoMode ~= nil then
		if MohoMode.animation then
			return relevance
		else
			return false
		end
	end
	return relevance
end

function LK_Curvature:OnInputDeviceEvent(moho, deviceEvent)
	return LM_TransformPoints:OnInputDeviceEvent(moho, deviceEvent)
end


function LK_Curvature:TestForClosestHandle(moho, mouseEvent)
	self.selID = -1
	self.handleSide = 0

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

	local g = mouseEvent.view:Graphics()
	local layerMatrix = LM.Matrix:new_local()
	moho.drawingLayer:GetFullTransform(moho.frame, layerMatrix, moho.document)
	g:Push()
	g:ApplyMatrix(layerMatrix)

	local minDistance = 1000000
	local markerR = 6
	local pt = LM.Point:new_local()
	if (self.showHandles) then
		if (MOHO.IsMohoPro()) then
			for i = 0, mesh:CountCurves() - 1 do
				local curve = mesh:Curve(i)
				for j = 0, curve:CountPoints() - 1 do
					if (not curve:Point(j).fHidden) then
						if (math.abs(curve:GetCurvature(j, moho.drawingLayerFrame)) > MOHO.PEAKED + 0.00001) then
							local testHandle = curve:GetControlHandle(j, moho.drawingLayerFrame, true)
							mouseEvent.view:Graphics():WorldToScreen(testHandle, pt)
							-- if (math.abs(pt.x - mouseEvent.pt.x) < markerR and math.abs(pt.y - mouseEvent.pt.y) < markerR and not(j == 0 and not curve.fClosed)) then
							if (not(j == 0 and not curve.fClosed)) then
								local distance = math.sqrt((pt.x - mouseEvent.pt.x) * (pt.x - mouseEvent.pt.x) + (pt.y - mouseEvent.pt.y) * (pt.y - mouseEvent.pt.y))
								if (distance < minDistance) then
									minDistance = distance
									self.selID = mesh:PointID(curve:Point(j))
									self.startHandle = testHandle
									self.handleSide = -1
									self.handleCurveID = i
									self.handlePointID = j
								end
							end

							testHandle = curve:GetControlHandle(j, moho.drawingLayerFrame, false)
							mouseEvent.view:Graphics():WorldToScreen(testHandle, pt)
							-- if (math.abs(pt.x - mouseEvent.pt.x) < markerR and math.abs(pt.y - mouseEvent.pt.y) < markerR and not(j == curve:CountPoints() - 1 and not curve.fClosed)) then
							if (not(j == curve:CountPoints() - 1 and not curve.fClosed)) then
								local distance = math.sqrt((pt.x - mouseEvent.pt.x) * (pt.x - mouseEvent.pt.x) + (pt.y - mouseEvent.pt.y) * (pt.y - mouseEvent.pt.y))
								if (distance < minDistance) then
									minDistance = distance
									self.selID = mesh:PointID(curve:Point(j))
									self.startHandle = testHandle
									self.handleSide = 1
									self.handleCurveID = i
									self.handlePointID = j
								end
							end
						end
					end
				end
			end
		end
	end
	local ptID = mesh:ClosestPoint(mouseEvent.startVec)
	if (ptID >= 0) then
		local selPt = mesh:Point(ptID)
		mouseEvent.view:Graphics():WorldToScreen(selPt.fPos, pt)
		local distance = math.sqrt((pt.x - mouseEvent.pt.x) * (pt.x - mouseEvent.pt.x) + (pt.y - mouseEvent.pt.y) * (pt.y - mouseEvent.pt.y))
		if (distance < minDistance) then
			minDistance = distance
			self.selID = ptID
			self.handleSide = 0
		end
	end
	g:Pop()
end

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

	self.selectMode = false
	if (mouseEvent.ctrlKey) then
		self.selectMode = true
		mouseEvent.ctrlKey = false
		LM_SelectPoints:OnMouseDown(moho, mouseEvent)
		return
	end

	moho.document:PrepUndo(moho.drawingLayer)
	moho.document:SetDirty()

	if self.lockedMode then
		mesh:SelectNone() -- * In case multiple points are selected.
	end

	self.isDragging = true
	self.selID = -1
	self.handleSide = 0
	self.selList = MOHO.SelectedPointList(mesh)
	self.numSel = #self.selList

	if (self.numSel < 2) then -- * Work with only the closest selection
		-- * Find the closest point here
		self:TestForClosestHandle(moho, mouseEvent)

		if (self.selID >= 0) then
			self.numSel = 1
			mesh:SelectNone()
			local pt = mesh:Point(self.selID)
			pt.fSelected = true
			moho:UpdateSelectedChannels()
		end
	end

	local curve = nil
	local ptPos = -1
	self.startPeaked = false

	if (mouseEvent.altKey) then
		self.startPeaked = true
	end

	if (self.numSel == 1) then
		local pt = mesh:Point(self.selID)
		for j = 0, pt:CountCurves() - 1 do
			curve, ptPos = pt:Curve(j, ptPos)
			local c = curve:GetCurvature(ptPos, moho.drawingLayerFrame)
			if (math.abs(c) > 0.0015) then
				self.startPeaked = false
			end
			if not self.lockedMode then
				curve:SetCurvature(ptPos, c, moho.drawingLayerFrame)
			end
		end
	else
		for i, pt in ipairs(self.selList) do
			for j = 0, pt:CountCurves() - 1 do
				curve, ptPos = pt:Curve(j, ptPos)
				local c = curve:GetCurvature(ptPos, moho.drawingLayerFrame)
				if (math.abs(c) > 0.0015) then
					self.startPeaked = false
				end
				if not self.lockedMode then
					curve:SetCurvature(ptPos, c, moho.drawingLayerFrame)
				end
			end
		end
	end

	self.savedVal = 0
	moho.drawingLayer:UpdateCurFrame()
	mouseEvent.view:DrawMe()
end

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

	if (self.selectMode) then
		mouseEvent.ctrlKey = false
		LM_SelectPoints:OnMouseMoved(moho, mouseEvent)
		return
	end

	local newVal = (mouseEvent.pt.x - mouseEvent.startPt.x) / mouseEvent.view:Graphics():Width()
	local curve = nil
	local ptPos = -1

	if (self.numSel == 1) then
		if (self.handleSide ~= 0) then
			-- * Moving a handle:
			local curve = mesh:Curve(self.handleCurveID)
			local syncHandles = true
			local constrainHandles = false
			if (mouseEvent.altKey) then
				syncHandles = false
			end
			if (mouseEvent.shiftKey) then
				constrainHandles = true
			end
			local handlePt = self.startHandle + (mouseEvent.vec - mouseEvent.startVec)
			if constrainHandles then
				local pt = mesh:Point(self.selID)
				local linePoint = pt.fPos
				local lineDirection = linePoint - self.startHandle
				handlePt = FO_Utilities:NearestPointOnLine(linePoint, lineDirection, handlePt)
			elseif (moho.gridOn) then
				moho:SnapToGrid(handlePt)
			end
			if (math.abs(curve:GetCurvature(self.handlePointID, moho.drawingLayerFrame)) < 0.001) then
				curve:SetCurvature(self.handlePointID, 0.001, moho.drawingLayerFrame)
			end
			curve:SetControlHandle(self.handlePointID, handlePt, moho.drawingLayerFrame, self.handleSide == -1, syncHandles)
		else
			--
			if not self.lockedMode then
			--
				local pt = mesh:Point(self.selID)
				for j = 0, pt:CountCurves() - 1 do
					curve, ptPos = pt:Curve(j, ptPos)
					local c = curve:GetCurvature(ptPos, moho.drawingLayerFrame)
					local negative = false
					if (c < 0.0) then
						negative = true
					end
					local weight = (curve:GetWeight(ptPos, moho.drawingLayerFrame, true) + curve:GetWeight(ptPos, moho.drawingLayerFrame, false)) / 2.0
					c = c + (newVal - self.savedVal) / weight
					if (negative) then
						c = LM.Clamp(c, -self.MAX_CURVATURE, -self.MIN_CURVATURE)
					else
						c = LM.Clamp(c, self.MIN_CURVATURE, self.MAX_CURVATURE)
					end
					curve:SetCurvature(ptPos, c, moho.drawingLayerFrame)
					if (self.startPeaked) then -- preserve the peaked nature of this control point
						curve:AimControlHandleAtNeighbor(ptPos, moho.drawingLayerFrame, true)
						curve:AimControlHandleAtNeighbor(ptPos, moho.drawingLayerFrame, false)
						curve:CorrectControlHandleAngles(ptPos, moho.drawingLayerFrame, true)
					end
				end
			--
			end
			--
		end
	else
		if not self.lockedMode then
			for i, pt in ipairs(self.selList) do
				for j = 0, pt:CountCurves() - 1 do
					curve, ptPos = pt:Curve(j, ptPos)
					local c = curve:GetCurvature(ptPos, moho.drawingLayerFrame)
					local negative = false
					if (c < 0.0) then
						negative = true
					end
					local weight = (curve:GetWeight(ptPos, moho.drawingLayerFrame, true) + curve:GetWeight(ptPos, moho.drawingLayerFrame, false)) / 2.0
					c = c + (newVal - self.savedVal) / weight
					if (negative) then
						c = LM.Clamp(c, -self.MAX_CURVATURE, -self.MIN_CURVATURE)
					else
						c = LM.Clamp(c, self.MIN_CURVATURE, self.MAX_CURVATURE)
					end
					curve:SetCurvature(ptPos, c, moho.drawingLayerFrame)
					if (self.startPeaked) then -- preserve the peaked nature of this control point
						curve:AimControlHandleAtNeighbor(ptPos, moho.drawingLayerFrame, true)
						curve:AimControlHandleAtNeighbor(ptPos, moho.drawingLayerFrame, false)
						curve:CorrectControlHandleAngles(ptPos, moho.drawingLayerFrame, true)
					end
				end
			end
		end
	end

	self.savedVal = newVal
	moho.drawingLayer:UpdateCurFrame()
	mouseEvent.view:DrawMe()
end

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

	-- if self.numSel < 1 and self.lockedMode then
	-- 	return
	-- end

	if (self.selectMode) then
		mouseEvent.ctrlKey = false
		LM_SelectPoints:OnMouseUp(moho, mouseEvent)
		self.selectMode = false
		self.selList = nil
		if (mouseEvent.pt.x == mouseEvent.startPt.x and mouseEvent.pt.y == mouseEvent.startPt.y) then
			mouseEvent.shiftKey = false
			mouseEvent.ctrlKey = false
			mouseEvent.altKey = false
			LM_SelectPoints:SingleClickSelect(moho, mouseEvent, false, true)
		end
		mouseEvent.view:DrawMe()
		return
	end

	if (self.numSel == 1 and self.handleSide ~= 0) then
		-- * Touched a handle
		local curve = mesh:Curve(self.handleCurveID)
		curve:CorrectControlHandleAngles(self.handlePointID, moho.drawingLayerFrame, true)
	end
	moho.drawingLayer:UpdateCurFrame()
	if (self.numSel == 1 and self.handleSide ~= 0) or not self.lockedMode then
		moho:NewKeyframe(CHANNEL_CURVE)
	end
	self.selList = nil
	self.isDragging = false

	moho:UpdateUI() -- * To check if new keys were made (which is too often!) TODO
end

function LK_Curvature:OnKeyDown(moho, keyEvent)
	LM_SelectPoints:OnKeyDown(moho, keyEvent)
end

function LK_Curvature:DrawMe(moho, view)
	if (moho:IsPlaying()) then
		return
	end

	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)
	if (self.selectMode) then
		LM_SelectPoints:DrawMe(moho, view)
	elseif (LM_TransformPoints.showHandles) then
		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 (math.abs(curve:GetCurvature(i, moho.drawingLayerFrame)) < 0.001) then
					firstPoint = true -- * DISABLING HANDLES BECAUSE CURVATURE IS SMALL
					lastPoint = true -- * DISABLING HANDLES BECAUSE CURVATURE IS SMALL
				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
	-- * Draw colored points:
	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 and not self.lockedMode then
						g:SetColor(MOHO.MohoGlobals.SelCol)
						g:DrawFatMarker(point.fPos.x, point.fPos.y, 5)
					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
	g:Pop()
end

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

LK_Curvature.PEAK =				MOHO.MSG_BASE
LK_Curvature.SMOOTH =			MOHO.MSG_BASE + 1
LK_Curvature.CHANGE =			MOHO.MSG_BASE + 2
LK_Curvature.NEGATIVE =			MOHO.MSG_BASE + 3
LK_Curvature.RESET =			MOHO.MSG_BASE + 4
LK_Curvature.DUMMY =			MOHO.MSG_BASE + 5
LK_Curvature.SHOWHANDLES =		MOHO.MSG_BASE + 6
LK_Curvature.LOCK_CURVATURE = 	MOHO.MSG_BASE + 7

LK_Curvature.SELECTITEM =		MOHO.MSG_BASE + 8 -- * 8++

LK_Curvature.showHandles =	false

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

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

	layout:AddChild(LM.GUI.StaticText(MOHO.Localize("/Scripts/Tool/Curvature/CurvatureField=Curvature:")))
	self.curvatureText = LM.GUI.TextControl(0, "00.0000", self.CHANGE, LM.GUI.FIELD_FLOAT)
	self.curvatureText:SetWheelInc(0.01)
	layout:AddChild(self.curvatureText)
	layout:AddPadding(-14)
	self.lockButton = LM.GUI.ImageButton("ScriptResources/FO_icons/lock", "Lock Curvature (Disables dragging curvature in case you only want to be able to touch the handles)", true, self.LOCK_CURVATURE, true)
	layout:AddChild(self.lockButton)

	FO_Utilities:Divider(layout, "Peak/Smooth/Invert/Reset")

	self.peak = LM.GUI.ImageButton("ScriptResources/peak", MOHO.Localize("/Scripts/Tool/Curvature/Peak=Peak"), false, self.PEAK, true)
	layout:AddChild(self.peak)

	self.smooth = LM.GUI.ImageButton("ScriptResources/smooth", MOHO.Localize("/Scripts/Tool/Curvature/Smooth=Smooth"), false, self.SMOOTH, true)
	layout:AddChild(self.smooth)

	layout:AddChild(LM.GUI.Button("+/-", self.NEGATIVE))

	self.resetButton = LM.GUI.ImageButton("ScriptResources/FO_icons/Reset", MOHO.Localize("/Scripts/Tool/Curvature/Reset=Reset"), false, self.RESET, true)
	layout:AddChild(self.resetButton)

	if (MOHO.IsMohoPro()) then
		FO_Utilities:Divider(layout, "Show")
		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)
	end
end

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

	if not LM_TransformPoints.showHandles then
		self.lockedMode = false
	end
	self.lockButton:SetValue(self.lockedMode)

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

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

	local curvature = 0
	local count = 0
	local selList = MOHO.SelectedPointList(mesh)
	local curve = nil
	local ptPos = -1

	for i, pt in ipairs(selList) do
		for j = 0, pt:CountCurves() - 1 do
			curve, ptPos = pt:Curve(j, ptPos)
			curvature = curvature + curve:GetCurvature(ptPos, moho.drawingLayerFrame)
			count = count + 1
		end
	end
	if (count < 1) then
		self.curvatureText:SetValue("")
	else
		curvature = curvature / count
		if (math.abs(curvature) < self.MIN_CURVATURE + 0.0001) then
			curvature = 0
		end
		self.curvatureText:SetValue(curvature)
	end

	self.curvatureText:Enable(not self.lockedMode)
	
	if (MOHO.IsMohoPro()) then
		self.showHandleCheck:SetValue(LM_TransformPoints.showHandles)
	end
end

function LK_Curvature:HandleMessage(moho, view, msg)
	local mesh = moho:DrawingMesh()
	if (mesh == nil) then
		return
	end
	if (msg == self.LOCK_CURVATURE) then
		self.lockedMode = not self.lockedMode
		moho:UpdateUI()
	elseif (msg == self.PEAK) 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:SetCurvature(MOHO.PEAKED, moho.drawingLayerFrame)
				pt:ResetControlHandles(moho.drawingLayerFrame)
			end
		end
		moho:NewKeyframe(CHANNEL_CURVE)
		moho:UpdateUI()
	elseif (msg == self.SMOOTH) 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:SetCurvature(MOHO.SMOOTH, moho.drawingLayerFrame)
				pt:ResetControlHandles(moho.drawingLayerFrame)
			end
		end
		moho:NewKeyframe(CHANNEL_CURVE)
		moho:UpdateUI()
	elseif (msg == self.CHANGE) then
		moho.document:PrepUndo(moho.drawingLayer)
		moho.document:SetDirty()
	
		local curvature = self.curvatureText:FloatValue()
		if (curvature > self.MAX_CURVATURE) then
			curvature = self.MAX_CURVATURE
		end
		if (curvature < -self.MAX_CURVATURE) then
			curvature = -self.MAX_CURVATURE
		end
		if (math.abs(curvature) < self.MIN_CURVATURE) then
			if (curvature < 0) then
				curvature = -self.MIN_CURVATURE
			else
				curvature = self.MIN_CURVATURE
			end
		end
		for i = 0, mesh:CountPoints() - 1 do
			local pt = mesh:Point(i)
			if (pt.fSelected) then
				pt:SetCurvature(curvature, moho.drawingLayerFrame)
			end
		end
		moho:NewKeyframe(CHANNEL_CURVE)
		moho:UpdateUI()
	elseif (msg == self.NEGATIVE) then
		moho.document:PrepUndo(moho.drawingLayer)
		moho.document:SetDirty()

		local curvature = 0
		local selList = MOHO.SelectedPointList(mesh)
		local curve = nil
		local ptPos = -1

		for i, pt in ipairs(selList) do
			for j = 0, pt:CountCurves() - 1 do
				curve, ptPos = pt:Curve(j, ptPos)
				curvature = curve:GetCurvature(ptPos, moho.drawingLayerFrame)
				curve:SetCurvature(ptPos, -curvature, moho.drawingLayerFrame)
			end
		end
		moho:NewKeyframe(CHANNEL_CURVE)
		moho:UpdateUI()
	elseif (msg == self.RESET) then
		moho.document:PrepUndo(moho.drawingLayer)
		moho.document:SetDirty()

		local curvature = 0
		local selList = MOHO.SelectedPointList(mesh)
		local curve = nil
		local ptPos = -1

		for i, pt in ipairs(selList) do
			for j = 0, pt:CountCurves() - 1 do
				curve, ptPos = pt:Curve(j, ptPos)
				if (moho.drawingLayerFrame == 0) then
					curve:SetCurvature(ptPos, MOHO.SMOOTH, moho.drawingLayerFrame)
					curve:ResetControlHandles(ptPos, moho.drawingLayerFrame)
				else
					curve:SetCurvature(ptPos, curve:GetCurvature(ptPos, 0), moho.drawingLayerFrame)
					
					curve:SetWeight(ptPos, curve:GetWeight(ptPos, 0, true), moho.drawingLayerFrame, true)
					curve:SetWeight(ptPos, curve:GetWeight(ptPos, 0, false), moho.drawingLayerFrame, false)
					
					curve:SetOffset(ptPos, curve:GetOffset(ptPos, 0, true), moho.drawingLayerFrame, true)
					curve:SetOffset(ptPos, curve:GetOffset(ptPos, 0, false), moho.drawingLayerFrame, false)
				end
			end
		end
		moho:NewKeyframe(CHANNEL_CURVE)
		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
	if (msg == self.SHOWHANDLES) then
		if (MOHO.IsMohoPro()) then
			self.showHandles = self.showHandleCheck:Value()
			LM_TransformPoints.showHandles = self.showHandles
			moho:UpdateUI()
		end
	end
end


-- ******************************************************************
-- Custom beziers in LM_TransformPoints overwrites original function!
-- ******************************************************************
function LM_TransformPoints:DrawMe(moho, view)
	if (moho:IsPlaying()) then
		return
	end
	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 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 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)

			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: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)

			g:Pop()
		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()
		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 -- * 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 (math.abs(curve:GetCurvature(i, moho.drawingLayerFrame)) < 0.001) then
					firstPoint = true -- * DISABLING HANDLES BECAUSE CURVATURE IS SMALL
					lastPoint = true -- * DISABLING HANDLES BECAUSE CURVATURE IS SMALL
				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
		g:Pop()
	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
			local g = view:Graphics()
			local layerMatrix = LM.Matrix:new_local()
			moho.drawingLayer:GetFullTransform(moho.frame, layerMatrix, moho.document)
			g:Push()
			g:ApplyMatrix(layerMatrix)
			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
			g:Pop()
		end
	end
	-- * end Lukas mod color points
end

Icon
LK_Curvature
Listed

Author: Lukas View Script
Script type: Tool

Uploaded: Jun 02 2022, 07:33

Last modified: Jun 11 2022, 06:00

Modded Curvature tool
Image

Shows bezier handles in a different shape and color than points, so it's easier to distinguish them. Option to lock curvature for situations where you only want to work with the bezier handles and not accidentally touch the points.

Also supports point color from the LK_SelectPoints tool.

Changelog:
v2.2 - Bugfix where handles and color points would sometimes be drawn at the wrong spot
v2.3 - The bezier handles and color points now also show up when using the standard Transform Points tool
v2.4 - HoldĀ  to constrain Handle direction
v2.5 - Fixed issue where you couldn't do anything when multiple points are selected with curvature locked
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: 74