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

ScriptName = "LM_TransformLayerModified"

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

LM_TransformLayerModified = {}

LM_TransformLayerModified.BASE_STR = 2370

function LM_TransformLayerModified:Name()
	return "Transform Layer"
end

function LM_TransformLayerModified:Version()
	return "9.0" -- modified for Moho 12
end

function LM_TransformLayerModified:IsBeginnerScript()
	return true
end

function LM_TransformLayerModified:Description()
	return MOHO.Localize("/Scripts/Tool/TransformLayer/Description=Transform entire layer (hold <shift> to constrain, <alt> to move forward and back, <ctrl/cmd> to edit motion path, <shift> + <alt> to move in Z and maintain visual size)")
end

function LM_TransformLayerModified:BeginnerDescription()
	return MOHO.Localize("/Scripts/Tool/TransformLayer/BeginnerDescription=Transform an entire layer or select multiple layers in the Layers window to transform them all at once. Transform your layer around on your screen and advance through the Timeline to animate your layer.")
end

function LM_TransformLayerModified:BeginnerDisabledDescription()
	return MOHO.Localize("/Scripts/Tool/TransformLayer/BeginnerDisabledDescription=")
end

function LM_TransformLayerModified:Creator()
	return "Smith Micro Software, Inc." -- modified by Mike Kelley, later by Stan
end

function LM_TransformLayerModified:UILabel()
	return(MOHO.Localize("/Scripts/Tool/TransformLayer/TransformLayer=Transform Layer"))
end

function LM_TransformLayerModified:LoadPrefs(prefs)
	self.displayOn = prefs:GetBool("LM_TransformLayerModified.displayOn", true)
end

function LM_TransformLayerModified:SavePrefs(prefs)
	prefs:SetBool("LM_TransformLayerModified.displayOn", self.displayOn)
end

function LM_TransformLayerModified:ResetPrefs()
	self.displayOn = true
end

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

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

LM_TransformLayerModified.dragging = false
LM_TransformLayerModified.keyMovement = false
LM_TransformLayerModified.selCount = 1
LM_TransformLayerModified.mode = 0 -- 0:translate, 1:rotate, 2:uniform scale, 3:x scale/x left, 4:y scale/y top, 5:x right, 6:y bottom
LM_TransformLayerModified.startVal = LM.Vector3:new_local()
LM_TransformLayerModified.dragPath = false
LM_TransformLayerModified.when = -10000
LM_TransformLayerModified.startAngle = 0
LM_TransformLayerModified.startScale = LM.Vector3:new_local()
LM_TransformLayerModified.matrix = LM.Matrix:new_local()
LM_TransformLayerModified.lastVec = LM.Vector2:new_local()
LM_TransformLayerModified.rotate3D = false
LM_TransformLayerModified.TOLERANCE = 10

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

function LM_TransformLayerModified:LayerBounds(moho, layer, frame, view)
	local halfWidth = 0.5
	local bbox = nil
	-- we used to not consider the contents of a group when showing a bounding box - maybe we should
--	if (layer:IsGroupType()) then
--		bbox = LM.BBox:new_local()
--		bbox.fMin:Set(-halfWidth, -halfWidth, 0.0)
--		bbox.fMax:Set(halfWidth, halfWidth, 0.0)
--	else
		bbox = layer:Bounds(frame)
		if (bbox.fMin.x > bbox.fMax.x or bbox.fMin.y > bbox.fMax.y or (bbox.fMax.x - bbox.fMin.x < 0.0001 and bbox.fMax.y - bbox.fMin.y < 0.0001)) then
			local origin = layer:Origin()
			bbox.fMin:Set(origin.x - halfWidth, origin.y - halfWidth, 0.0)
			bbox.fMax:Set(origin.x + halfWidth, origin.y + halfWidth, 0.0)
		end
--	end
	halfWidth = ((bbox.fMax.x - bbox.fMin.x) + (bbox.fMax.y - bbox.fMin.y)) / 4.0
	local origin = layer:Origin()
	if (bbox.fMin.x > origin.x) then
		bbox.fMin.x = origin.x - halfWidth
	end
	if (bbox.fMin.y > origin.y) then
		bbox.fMin.y = origin.y - halfWidth
	end
	if (bbox.fMax.x < origin.x) then
		bbox.fMax.x = origin.x + halfWidth
	end
	if (bbox.fMax.y < origin.y) then
		bbox.fMax.y = origin.y + halfWidth
	end

	-- 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 LM_TransformLayerModified:TestMousePoint(moho, mouseEvent)
	if (self.keyMovement) then
		return 0
	end

	-- Returns what mode the tool would be in if the user clicked at the current mouse location	
	local markerR = 4
	local bbox = self:LayerBounds(moho, moho.layer, moho.frame, mouseEvent.view)
	local v = LM.Vector2:new_local()
	local pt = LM.Point:new_local()
	local m = LM.Matrix:new_local()

	moho.layer: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
		return 2
	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
		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
		return 2
	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
		return 2
	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
		return 3
	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
		return 5
	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
		return 6
	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
		return 4
	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.vec.x < bbox.fMin.x - rotWidth or mouseEvent.vec.x > bbox.fMax.x + rotWidth or mouseEvent.vec.y < bbox.fMin.y - rotWidth or mouseEvent.vec.y > bbox.fMax.y + rotWidth) then
		return 0
	end
	
	-- test for rotation
	if (mouseEvent.vec.x < bbox.fMin.x or mouseEvent.vec.x > bbox.fMax.x or mouseEvent.vec.y < bbox.fMin.y or mouseEvent.vec.y > bbox.fMax.y) then
		return 1
	end

	return 0 -- translation inside the bounding box
end

function LM_TransformLayerModified:OnMouseDown(moho, mouseEvent)
	self.dragging = true
	self.mode = 0 -- translate by default
	self.selCount = moho.document:CountSelectedLayers()
	self.dragPath = false
	self.depthShift = false

	moho.document:PrepMultiUndo(true)
	moho.document:SetDirty()

	self.selCount = moho.document:CountSelectedLayers()
	self.when = -10000
	if (mouseEvent.ctrlKey and self.selCount == 1) then -- Not sure if this should require a modifier key or not...
		self.when = -20000
		local g = mouseEvent.view:Graphics()
		local m = LM.Matrix:new_local()
		local vec = LM.Vector2:new_local()
		local origin = moho.layer:Origin()
		local pt = LM.Point:new_local()
		local totalTimingOffset = moho.layer:TotalTimingOffset()
		-- First see if any keyframes were picked
		for i = 0, moho.layer.fTranslation:CountKeys() - 1 do
			local frame = moho.layer.fTranslation:GetKeyWhen(i)
			moho.layer:GetFullTransform(frame - totalTimingOffset, m, moho.document)
			vec:Set(origin.x, origin.y)
			m:Transform(vec)
			if (moho.layer.fTranslation:HasKey(frame)) then
				g:WorldToScreen(vec, pt)
				if (math.abs(pt.x - mouseEvent.startPt.x) < self.TOLERANCE and math.abs(pt.y - mouseEvent.startPt.y) < self.TOLERANCE) then
					self.when = frame
					break
				end
			end
		end
		-- If no keyframes were picked, try picking a random point along the curve.
		if (self.when <= -10000) then
			local startFrame = moho.layer.fTranslation:GetKeyWhen(0)
			local endFrame = moho.layer.fTranslation:Duration()
			if (endFrame > startFrame) then
				local vec3 = LM.Vector3:new_local()
				local oldVec3 = LM.Vector3:new_local()
				g:Clear(0, 0, 0, 0)
				g:SetColor(255, 255, 255)
				g:BeginPicking(mouseEvent.startPt, 4)
				for frame = startFrame, endFrame do
					moho.layer:GetFullTransform(frame - totalTimingOffset, m, moho.document)
					vec3:Set(origin.x, origin.y, 0)
					m:Transform(vec3)
					if (frame > startFrame) then
						g:DrawLine(oldVec3.x, oldVec3.y, vec3.x, vec3.y)
					end
					if (g:Pick()) then
						self.when = frame
						break
					end
					oldVec3:Set(vec3)
				end
				if (self.when > -10000) then -- We picked a point on the curve.
					if (self.selCount > 1) then
						return
					end
					moho.layer.fTranslation:AddKey(self.when)
				end
			end
		end
		if (self.when > -10000) then
			self.dragPath = true
		end
	end

	if (self.when == -20000) then
		return
	end

	if (not self.dragPath) then
		self.mode = self:TestMousePoint(moho, mouseEvent)
	end

	if (self.mode == 0) then -- translate
		self.startVal = {}
		if (self.when > -10000) then
			local startVal = LM.Vector3:new_local()
			startVal:Set(moho.layer.fTranslation:GetValue(self.when))
			table.insert(self.startVal, startVal)
			-- Put the keyframe time in terms of document time, so that it works as expected during mouse dragging.
			self.when = self.when - moho.layer:TotalTimingOffset()
		else
			self.startVal = {}
			for i = 0, self.selCount - 1 do
				local layer = moho.document:GetSelectedLayer(i)
				local startVal = LM.Vector3:new_local()
				startVal:Set(layer.fTranslation.value)
				table.insert(self.startVal, startVal)
				if (not layer:IsAncestorSelected()) then
					layer.fTranslation:AddKey(moho.frame + layer:TotalTimingOffset())
				end
			end -- for i
		end
		if (mouseEvent.altKey and mouseEvent.shiftKey) then
			self.depthShift = true
			self.startScale = LM.Vector3:new_local()
			self.startScale:Set(moho.layer.fScale.value)
			self.layerScales = {}
			for i = 0, self.selCount - 1 do
				local layer = moho.document:GetSelectedLayer(i)
				local layerScale = LM.Vector3:new_local()
				layerScale:Set(layer.fScale.value)
				table.insert(self.layerScales, layerScale)
			end
		end
	elseif (self.mode == 1) then -- rotate
		self.startAngle = moho.layer.fRotationZ.value
		self.originalAngle = moho.layer.fRotationZ.value
		self.layerAngles = {}
		for i = 0, self.selCount - 1 do
			local layer = moho.document:GetSelectedLayer(i)
			table.insert(self.layerAngles, layer.fRotationZ.value)
			if (not layer:IsAncestorSelected()) then
				layer.fRotationZ:AddKey(moho.frame + layer:TotalTimingOffset())
			end
		end -- for i
	else -- scale
		self.startScale = LM.Vector3:new_local()
		self.startScale:Set(moho.layer.fScale.value)
		self.layerScales = {}
		for i = 0, self.selCount - 1 do
			local layer = moho.document:GetSelectedLayer(i)
			local layerScale = LM.Vector3:new_local()
			layerScale:Set(layer.fScale.value)
			table.insert(self.layerScales, layerScale)
			if (not layer:IsAncestorSelected()) then
				layer.fScale:AddKey(moho.frame + layer:TotalTimingOffset())
			end
		end -- for i
	end

	moho.layer:GetFullTransform(moho.frame, self.matrix, moho.document)
	self.lastVec:Set(mouseEvent.view:Point2Vec(mouseEvent.pt, self.matrix))

	mouseEvent.view:DrawMe()
end

function LM_TransformLayerModified:OnMouseMoved(moho, mouseEvent)
	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 == 0) then
		self:OnMouseMoved_T(moho, mouseEvent)
	elseif (self.mode == 1) then
		self:OnMouseMoved_R(moho, mouseEvent)
	else
		self:OnMouseMoved_S(moho, mouseEvent)
	end
end

function LM_TransformLayerModified:OnMouseMoved_T(moho, mouseEvent)
	if (self.when == -20000) then
		return
	end
	local frame = self.when
	if (frame > -10000 and self.selCount > 1) then
		return
	end
	if (frame <= -10000) then
		frame = moho.frame
	end

	if (not moho.layer:IsImmuneToCamera()) then
		local v1 = LM.Vector3:new_local()
		local v2 = LM.Vector3:new_local()
		local vec = LM.Vector3:new_local()
		local origin = LM.Vector3:new_local()
		local m = LM.Matrix:new_local()

		for i = 0, self.selCount - 1 do
			local layer = moho.document:GetSelectedLayer(i)
			
			if (not layer:IsAncestorSelected()) then
				local tempO = moho.layer:Origin()
				origin:Set(tempO.x, tempO.y, 0)
				moho.layer:GetFullTransform(moho.frame, m, moho.document)
				m:Transform(origin)

				m:Identity()
				if (layer:Parent()) then
					layer:Parent():GetFullTransform(moho.frame, m, moho.document)
				elseif (moho.document:IsOutsideViewEnabled()) then
					moho.document:GetOutsideViewMatrix(m)
				else
					moho.document:GetCameraMatrix(moho.frame, m)
				end
				if (layer:LayerParentBone() ~= -1 and layer:Parent() and layer:Parent():IsBoneType()) then
					local parentM = LM.Matrix:new_local()
					layer:GetParentBoneTransform(moho.frame, parentM, moho.document)
					m:Multiply(parentM)
				end
				m:Invert()
				mouseEvent.view:Graphics():ScreenToWorld(mouseEvent.startPt, v1)
				mouseEvent.view:Graphics():ScreenToWorld(mouseEvent.pt, v2)

				if (not mouseEvent.altKey) then
					v1.z = origin.z
					v2.z = origin.z
				end

				vec.x = v2.x - v1.x
				vec.y = v2.y - v1.y
	
				if (mouseEvent.altKey) then
					v2.z = v1.z - (v2.y - v1.y) * 0.25
					v2.x = 0.0
					v1.x = 0.0
					v2.y = 0.0
					v1.y = 0.0
				else
					if (mouseEvent.shiftKey) then
						if (math.abs(vec.x) > math.abs(vec.y)) then
							v2.y = v1.y
						else
							v2.x = v1.x
						end
					end
				end

				if (mouseEvent.altKey) then
					v1.z = v1.z - 0.885 ---0.95
					v2.z = v2.z - 0.885 ---0.95
				end

				m:Transform(v1)
				m:Transform(v2)

				if (self.depthShift) then -- move in Z, but try to preserve the visual size of the layer
					local layerFrame = frame + layer:TotalTimingOffset()
					vec = v2 - v1
					local distance = vec:Mag()
					local layerM = LM.Matrix:new_local()
					local parentM = LM.Matrix:new_local()
					layer.fTranslation:SetValue(layerFrame, self.startVal[i + 1])
					layer:GetFullTransform(frame, layerM, nil)
					if (layer:Parent()) then
						layer:Parent():GetFullTransform(frame, parentM, nil)
					end
					local origin2D = layer:Origin()
					local origin = LM.Vector3:new_local()
					origin:Set(origin2D.x, origin2D.y, 0)
					layerM:Transform(origin)
					vec = origin - moho.document.fCameraTrack.value
					vec:NormMe()
					if (v2.z > v1.z) then
						distance = -distance
					end
					vec = origin + vec * distance
					parentM:Invert()
					parentM:Transform(origin)
					parentM:Transform(vec)
					layer.fTranslation:SetValue(layerFrame, self.startVal[i + 1] + vec - origin)
				else
					vec = self.startVal[i + 1] + v2 - v1
					layer.fTranslation:SetValue(frame + layer:TotalTimingOffset(), vec)
				end
			end
		end -- for i
	else
		for i = 0, self.selCount - 1 do
			local layer = moho.document:GetSelectedLayer(i)
			if (not layer:IsAncestorSelected()) then
				layer.fTranslation:SetValue(frame + layer:TotalTimingOffset(), self.startVal[i + 1])

				local layerM = LM.Matrix:new_local()
				local tempLayer = MOHO.MohoLayer:new_local()
				tempLayer.fTranslation:SetValue(0, layer.fTranslation.value)

				if (layer:Parent()) then
					local parentM = LM.Matrix:new_local()
					tempLayer:GetFullTransform(moho.frame, layerM, nil)
					layer:Parent():GetFullTransform(moho.frame, parentM, moho.document)
					layerM:Multiply(parentM)
					if (layer:LayerParentBone() ~= -1 and layer:Parent():IsBoneType()) then
						layer:GetParentBoneTransform(moho.frame, parentM, moho.document)
						layerM:Multiply(parentM)
					end
				elseif (layer:IsImmuneToCamera()) then
					tempLayer:GetFullTransform(moho.frame, layerM, nil)
				else
					tempLayer:GetFullTransform(moho.frame, layerM, moho.document)
				end
				self.startVec = mouseEvent.view:Point2Vec(mouseEvent.startPt, layerM)
				self.nextVec = mouseEvent.view:Point2Vec(mouseEvent.pt, layerM)
	
				local vec = LM.Vector3:new_local()

				if (mouseEvent.altKey) then
					mouseEvent.view:Graphics():ScreenToWorld(mouseEvent.startPt, self.startVec)
					mouseEvent.view:Graphics():ScreenToWorld(mouseEvent.pt, self.nextVec)
					vec:Set(self.startVal[i + 1])
					vec.z = self.startVal[i + 1].z - (self.nextVec.y - self.startVec.y)
					layer.fTranslation:SetValue(frame + layer:TotalTimingOffset(), vec)
	
					if (layer:Parent()) then
						layer:Parent():DepthSort(moho.document)
					end
				else
					vec.x = self.nextVec.x - self.startVec.x
					vec.y = self.nextVec.y - self.startVec.y
	
					if (mouseEvent.shiftKey) then
						if (math.abs(vec.x) > math.abs(vec.y)) then
							vec.y = 0
						else
							vec.x = 0
						end
					end

					vec = vec + self.startVal[i + 1]
					layer.fTranslation:SetValue(frame + layer:TotalTimingOffset(), vec)
				end
			end
		end -- for i
	end

	if (frame ~= moho.frame) then
		moho:SetCurFrame(moho.frame) -- force a refresh when editing a key at a different frame
	end

	moho.document:DepthSort()
	mouseEvent.view:DrawMe()
end

function LM_TransformLayerModified:OnMouseMoved_S(moho, mouseEvent)
	moho.layer.fScale:SetValue(moho.layerFrame, self.startScale)

	local startVec = mouseEvent.view:Point2Vec(mouseEvent.startPt, self.matrix)
	local nextVec = mouseEvent.view:Point2Vec(mouseEvent.pt, self.matrix)

	-- scaling connected to actual drag amount
	local origin = moho.layer:Origin()
	local v1 = startVec - origin
	local v2 = nextVec - origin
	local scaling = LM.Vector3:new_local()
	scaling:Set(v2.x / v1.x, v2.y / v1.y, 1)

	if (self.mode == 3 or self.mode == 5) then -- X scaling
		scaling.y = 1
		if (mouseEvent.shiftKey) then
			scaling.y = 1 / scaling.x
		end
	elseif (self.mode == 4 or self.mode == 6) then -- Y scaling
		scaling.x = 1
		if (mouseEvent.shiftKey) then
			scaling.x = 1 / scaling.y
		end
	elseif (self.mode == 2) then
		scaling.z = (scaling.x + scaling.y) / 2
		if (not mouseEvent.shiftKey) then
			scaling.x = scaling.z
			scaling.y = scaling.z
		end
	else
		scaling.z = v2:Mag() / v1:Mag()
		scaling.x = scaling.z
		scaling.y = scaling.z
	end

	local vec = LM.Vector3:new_local()
	for i = 0, self.selCount - 1 do
		local layer = moho.document:GetSelectedLayer(i)
		if (not layer:IsAncestorSelected()) then
			vec:Set(self.layerScales[i + 1])
			vec.x = vec.x * scaling.x
			vec.y = vec.y * scaling.y
			vec.z = vec.z * scaling.z
			layer.fScale:SetValue(moho.frame + layer:TotalTimingOffset(), vec)
		end
	end

	moho.document:DepthSort()
	mouseEvent.view:DrawMe()
end

function LM_TransformLayerModified:OnMouseMoved_R(moho, mouseEvent)
	local nextVec = mouseEvent.view:Point2Vec(mouseEvent.pt, self.matrix)
	local angle = self.startAngle
	local origin = moho.layer:Origin()
	local v1 = self.lastVec - origin
	local v2 = nextVec - origin
	v2:Rotate(-math.atan2(v1.y, v1.x))

	local dAngle = math.atan2(v2.y, v2.x)
	local flipH = moho.layer.fFlipH.value
	local flipV = moho.layer.fFlipV.value
	if ((flipH and not flipV) or (flipV and not flipH)) then
		dAngle = -dAngle
	end

	self.startAngle = angle + dAngle

	for i = 0, self.selCount - 1 do
		local layer = moho.document:GetSelectedLayer(i)
		if (not layer:IsAncestorSelected()) then
			angle = self.layerAngles[i + 1]
			angle = angle + dAngle
			self.layerAngles[i + 1] = angle
			if (mouseEvent.shiftKey) then
				angle = angle / (math.pi / 4)
				angle = (math.pi / 4) * LM.Round(angle)
			end
			layer.fRotationZ:SetValue(moho.frame + layer:TotalTimingOffset(), angle)
		end
	end

	self.lastVec:Set(nextVec)
	moho.document:DepthSort()

	mouseEvent.view:DrawMe()
end

function LM_TransformLayerModified:OnMouseUp(moho, mouseEvent)
	if (not self.dragging) then
		return
	end

	if (self.when == -2) then
		return
	end

	if (self.when < 0) then
		if (self.mode == 0) then
			if (MOHO.MohoGlobals.EditMultipleKeys) then
				for i = 0, self.selCount - 1 do
					local layer = moho.document:GetSelectedLayer(i)
					if (not layer:IsAncestorSelected()) then
						local timingOffset = layer:TotalTimingOffset()
						local offset = layer.fTranslation:GetValue(moho.frame + timingOffset)
						if (self.startVal[i + 1] ~= nil) then
							offset = offset - self.startVal[i + 1]
						end

						for j = 0, layer.fTranslation:CountKeys() - 1 do
							if (layer.fTranslation:IsKeySelectedByID(j) and layer.fTranslation:GetKeyWhen(j) ~= moho.frame + timingOffset) then
								local vec = layer.fTranslation:GetValueByID(j) + offset
								layer.fTranslation:SetValueByID(j, vec)
							end
						end
					end
				end -- for i
			end
			moho:NewKeyframe(CHANNEL_LAYER_T)
			if (self.depthShift) then
				local frame = self.when
				if (frame <= -10000) then
					frame = moho.frame
				end
				for i = 0, self.selCount - 1 do
					local layer = moho.document:GetSelectedLayer(i)
					if (not layer:IsAncestorSelected()) then
						local layerFrame = frame + layer:TotalTimingOffset()
						local scale = 1
					
						local newTranslation = LM.Vector3:new_local()
						newTranslation:Set(layer.fTranslation.value)
					
						local layerM = LM.Matrix:new_local()
						layer.fTranslation:SetValue(layerFrame, self.startVal[i + 1])
						layer:GetFullTransform(frame, layerM, nil)
						local origin2D = layer:Origin()
						local oldOrigin = LM.Vector3:new_local()
						oldOrigin:Set(origin2D.x, origin2D.y, 0)
						layerM:Transform(oldOrigin)
					
						layer.fTranslation:SetValue(layerFrame, newTranslation)
						layer:GetFullTransform(frame, layerM, nil)
						origin2D = layer:Origin()
						local newOrigin = LM.Vector3:new_local()
						newOrigin:Set(origin2D.x, origin2D.y, 0)
						layerM:Transform(newOrigin)

						scale = (newOrigin - moho.document.fCameraTrack.value):Mag() / (oldOrigin - moho.document.fCameraTrack.value):Mag()
					
						local vec = self.layerScales[i + 1] * scale
						layer.fScale:SetValue(layerFrame, vec)
					end
				end -- for i
				if (MOHO.MohoGlobals.EditMultipleKeys) then
					local offset = LM.Vector3:new_local()
					offset.x = moho.layer.fScale.value.x / self.startScale.x
					offset.y = moho.layer.fScale.value.y / self.startScale.y
					offset.z = moho.layer.fScale.value.z / self.startScale.z
					for i = 0, self.selCount - 1 do
						local layer = moho.document:GetSelectedLayer(i)
						if (not layer:IsAncestorSelected()) then
							local timingOffset = layer:TotalTimingOffset()
							offset.x = layer.fScale.value.x / self.layerScales[i + 1].x
							offset.y = layer.fScale.value.y / self.layerScales[i + 1].y
							offset.z = layer.fScale.value.z / self.layerScales[i + 1].z
							for j = 0, layer.fScale:CountKeys() - 1 do
								if (layer.fScale:IsKeySelectedByID(j) and layer.fScale:GetKeyWhen(j) ~= moho.frame + timingOffset) then
									local scale = layer.fScale:GetValueByID(j)
									scale.x = scale.x * offset.x
									scale.y = scale.y * offset.y
									scale.z = scale.z * offset.z
									layer.fScale:SetValueByID(j, scale)
								end
							end
						end
					end -- for i
				end
				moho:NewKeyframe(CHANNEL_LAYER_S)
			end
		elseif (self.mode == 1) then
			if (MOHO.MohoGlobals.EditMultipleKeys) then
				local offset = moho.layer.fRotationZ.value
				if (self.originalAngle ~= nil) then
					offset = offset - self.originalAngle
				end
				for i = 0, self.selCount - 1 do
					local layer = moho.document:GetSelectedLayer(i)
					if (not layer:IsAncestorSelected()) then
						local timingOffset = layer:TotalTimingOffset()
						for j = 0, layer.fRotationZ:CountKeys() - 1 do
							if (layer.fRotationZ:IsKeySelectedByID(j) and layer.fRotationZ:GetKeyWhen(j) ~= moho.frame + timingOffset) then
								local angle = layer.fRotationZ:GetValueByID(j) + offset
								layer.fRotationZ:SetValueByID(j, angle)
							end
						end
					end
				end -- for i
			end
			moho:NewKeyframe(CHANNEL_LAYER_ROT_Z)
		else
			if (MOHO.MohoGlobals.EditMultipleKeys) then
				local offset = LM.Vector3:new_local()
				offset.x = moho.layer.fScale.value.x / self.startScale.x
				offset.y = moho.layer.fScale.value.y / self.startScale.y
				offset.z = moho.layer.fScale.value.z / self.startScale.z
				for i = 0, self.selCount - 1 do
					local layer = moho.document:GetSelectedLayer(i)
					if (not layer:IsAncestorSelected()) then
						local timingOffset = layer:TotalTimingOffset()
						offset.x = layer.fScale.value.x / self.layerScales[i + 1].x
						offset.y = layer.fScale.value.y / self.layerScales[i + 1].y
						offset.z = layer.fScale.value.z / self.layerScales[i + 1].z
						for j = 0, layer.fScale:CountKeys() - 1 do
							if (layer.fScale:IsKeySelectedByID(j) and layer.fScale:GetKeyWhen(j) ~= moho.frame + timingOffset) then
								local scale = layer.fScale:GetValueByID(j)
								scale.x = scale.x * offset.x
								scale.y = scale.y * offset.y
								scale.z = scale.z * offset.z
								layer.fScale:SetValueByID(j, scale)
							end
						end
					end
				end -- for i
			end
			moho:NewKeyframe(CHANNEL_LAYER_S)
		end
	end
	moho:UpdateUI()
	self.dragging = false
end

function LM_TransformLayerModified:OnKeyDown(moho, keyEvent)
	if (keyEvent.ctrlKey) then
		local inc = 1
		if (keyEvent.shiftKey) then
			inc = 10
		end

		local m = LM.Matrix:new_local()
		moho.layer: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.startVec = keyEvent.view:Point2Vec(fakeME.pt, m)
		fakeME.shiftKey = false
		fakeME.ctrlKey = false
		fakeME.altKey = keyEvent.altKey
		fakeME.penPressure = 0

		self.keyMovement = true

		if (keyEvent.keyCode == LM.GUI.KEY_UP) then
			self:OnMouseDown(moho, fakeME)
			fakeME.pt.y = fakeME.pt.y - inc
			fakeME.vec = keyEvent.view:Point2Vec(fakeME.pt, m)
			self:OnMouseMoved(moho, fakeME)
			self:OnMouseUp(moho, fakeME)
		elseif (keyEvent.keyCode == LM.GUI.KEY_DOWN) then
			self:OnMouseDown(moho, fakeME)
			fakeME.pt.y = fakeME.pt.y + inc
			fakeME.vec = keyEvent.view:Point2Vec(fakeME.pt, m)
			self:OnMouseMoved(moho, fakeME)
			self:OnMouseUp(moho, fakeME)
		elseif (keyEvent.keyCode == LM.GUI.KEY_LEFT) then
			self:OnMouseDown(moho, fakeME)
			fakeME.pt.x = fakeME.pt.x - inc
			fakeME.vec = keyEvent.view:Point2Vec(fakeME.pt, m)
			self:OnMouseMoved(moho, fakeME)
			self:OnMouseUp(moho, fakeME)
		elseif (keyEvent.keyCode == LM.GUI.KEY_RIGHT) then
			self:OnMouseDown(moho, fakeME)
			fakeME.pt.x = fakeME.pt.x + inc
			fakeME.vec = keyEvent.view:Point2Vec(fakeME.pt, m)
			self:OnMouseMoved(moho, fakeME)
			self:OnMouseUp(moho, fakeME)
		end

		self.keyMovement = false
	end
end

function LM_TransformLayerModified:OnInputDeviceEvent(moho, deviceEvent)
	if (deviceEvent.inputData:GetString("DeviceType") == "Wacom Multitouch") then
		local mtState = deviceEvent.inputData:GetInt("MultitouchState")
		local mtMousePt = deviceEvent.inputData:GetPoint("MultitouchCenterPoint")
		if (mtState == 1) then -- first finger down
			self.rotate3D = false
			self.mtFingersTouching = 0 -- we'll get the correct number on the next event
			self.mtTranslate = LM.Point:new_local()
			self.mtStartPt = deviceEvent.inputData:GetPoint("MultitouchCenterPoint")
			self.mtAccumTranslate = LM.Point: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
			
			self.selCount = moho.document:CountSelectedLayers()
			self.when = -10000
			
			moho.document:SetDirty()
			moho.document:PrepMultiUndo(true)
			
			self.startVal = {}
			self.startAngle = {}
			self.startScale = {}
			self.startAngleX = {}
			self.startAngleY = {}
			for i = 0, self.selCount - 1 do
				local layer = moho.document:GetSelectedLayer(i)
				local startVal = LM.Vector3:new_local()
				startVal:Set(layer.fTranslation.value)
				table.insert(self.startVal, startVal)
				layer.fTranslation:AddKey(moho.frame + layer:TotalTimingOffset())
				table.insert(self.startAngle, layer.fRotationZ.value)
				table.insert(self.startAngleX, layer.fRotationX.value)
				table.insert(self.startAngleY, layer.fRotationY.value)
				startVal = LM.Vector3:new_local()
				startVal:Set(layer.fScale.value)
				table.insert(self.startScale, startVal)
			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
			if (self.when < 0) then
				moho:NewKeyframe(CHANNEL_LAYER_T)
			end
			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.mtStartPt = deviceEvent.inputData:GetPoint("MultitouchCenterPoint")
				self.mtStartAngle = deviceEvent.inputData:GetFloat("MultitouchAngle")
				self.mtStartScale = deviceEvent.inputData:GetFloat("MultitouchScale")
			end
			
			self.mtTranslate = mtMousePt - self.mtStartPt
			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
			
			local frame = self.when
			if (frame <= -10000) then
				frame = moho.frame
			end
			
			if (deviceEvent.inputData:GetBool("DoubleTouch")) then
				if (fingersTouching == 2) then
					self:HandleMessage(moho, deviceEvent.view, self.RESET_T)
					self:HandleMessage(moho, deviceEvent.view, self.RESET_S)
					self:HandleMessage(moho, deviceEvent.view, self.RESET_R)
					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

			if (self.mtFingersTouching >= 4 or self.rotate3D) then
				self.rotate3D = true
				for i = 0, self.selCount - 1 do
					local layer = moho.document:GetSelectedLayer(i)
					local angleX = self.startAngleX[i + 1]
					angleX = angleX + math.pi * (mtMousePt.y + self.mtAccumTranslate.y - self.mtStartPt.y) / deviceEvent.view:Graphics():Height()
					local angleY = self.startAngleY[i + 1]
					angleY = angleY + math.pi * (mtMousePt.x + self.mtAccumTranslate.x - self.mtStartPt.x) / deviceEvent.view:Graphics():Width()
					layer.fRotationX:SetValue(frame + layer:TotalTimingOffset(), angleX)
					layer.fRotationY:SetValue(frame + layer:TotalTimingOffset(), angleY)
				end
				if (frame ~= moho.frame) then
					moho:SetCurFrame(moho.frame) -- force a refresh when editing a key at a different frame
				end
				moho.document:DepthSort()
			elseif (not moho.layer:IsImmuneToCamera()) then
				local v1 = LM.Vector3:new_local()
				local v2 = LM.Vector3:new_local()
				local vec = LM.Vector3:new_local()
				local m = LM.Matrix:new_local()
		
				for i = 0, self.selCount - 1 do
					local layer = moho.document:GetSelectedLayer(i)
		
					m:Identity()
					if (layer:Parent()) then
						layer:Parent():GetFullTransform(moho.frame, m, moho.document)
					elseif (moho.document:IsOutsideViewEnabled()) then
						moho.document:GetOutsideViewMatrix(m)
					else
						moho.document:GetCameraMatrix(moho.frame, m)
					end
					if (layer:LayerParentBone() ~= -1 and layer:Parent() and layer:Parent():IsBoneType()) then
						local parentM = LM.Matrix:new_local()
						layer:GetParentBoneTransform(moho.frame, parentM, moho.document)
						m:Multiply(parentM)
					end
					m:Invert()
					deviceEvent.view:Graphics():ScreenToWorld(self.mtStartPt, v1)
					deviceEvent.view:Graphics():ScreenToWorld(mtMousePt + self.mtAccumTranslate, v2)
			
					vec.x = v2.x - v1.x
					vec.y = v2.y - v1.y
		
					if (deviceEvent.altKey) then
						v2.z = v1.z - (v2.y - v1.y) * 0.25
						v2.x = 0.0
						v1.x = 0.0
						v2.y = 0.0
						v1.y = 0.0
					else
						if (deviceEvent.shiftKey) then
							if (math.abs(vec.x) > math.abs(vec.y)) then
								v2.y = v1.y
							else
								v2.x = v1.x
							end
						end
					end
					
					v1.z = v1.z - 0.885 ---0.95
					v2.z = v2.z - 0.885 ---0.95
		
					m:Transform(v1)
					m:Transform(v2)
			
					vec = self.startVal[i + 1] + v2 - v1
					layer.fTranslation:SetValue(frame + layer:TotalTimingOffset(), vec)
					if (self.mtFingersTouching > 1) then
						layer.fRotationZ:SetValue(frame + layer:TotalTimingOffset(), self.startAngle[i + 1] + self.mtAccumAngle + self.mtAngle)
						layer.fScale:SetValue(frame + layer:TotalTimingOffset(), self.startScale[i + 1] * self.mtAccumScale * self.mtScale)
					end
				end -- for i
			else
				for i = 0, self.selCount - 1 do
					local layer = moho.document:GetSelectedLayer(i)
					layer.fTranslation:SetValue(frame + layer:TotalTimingOffset(), self.startVal[i + 1])
		
					local layerM = LM.Matrix:new_local()
					local tempLayer = MOHO.MohoLayer:new_local()
					tempLayer.fTranslation:SetValue(0, layer.fTranslation.value)
		
					if (layer:Parent()) then
						local parentM = LM.Matrix:new_local()
						tempLayer:GetFullTransform(moho.frame, layerM, nil)
						layer:Parent():GetFullTransform(moho.frame, parentM, moho.document)
						layerM:Multiply(parentM)
						if (layer:LayerParentBone() ~= -1 and layer:Parent():IsBoneType()) then
							layer:GetParentBoneTransform(moho.frame, parentM, moho.document)
							layerM:Multiply(parentM)
						end
					elseif (layer:IsImmuneToCamera()) then
						tempLayer:GetFullTransform(moho.frame, layerM, nil)
					else
						tempLayer:GetFullTransform(moho.frame, layerM, moho.document)
					end
					self.startVec = deviceEvent.view:Point2Vec(self.mtStartPt, layerM)
					self.nextVec = deviceEvent.view:Point2Vec(mtMousePt + self.mtAccumTranslate, layerM)
			
					local vec = LM.Vector3:new_local()
		
					if (deviceEvent.altKey) then
						deviceEvent.view:Graphics():ScreenToWorld(self.mtStartPt, self.startVec)
						deviceEvent.view:Graphics():ScreenToWorld(mtMousePt, self.nextVec)
						vec:Set(self.startVal[i + 1])
						vec.z = self.startVal[i + 1].z - (self.nextVec.y - self.startVec.y)
						layer.fTranslation:SetValue(frame + layer:TotalTimingOffset(), vec)
			
						if (layer:Parent()) then
							layer:Parent():DepthSort(moho.document)
						end
					else
						vec.x = self.nextVec.x - self.startVec.x
						vec.y = self.nextVec.y - self.startVec.y
						
						if (deviceEvent.shiftKey) then
							if (math.abs(vec.x) > math.abs(vec.y)) then
								vec.y = 0
							else
								vec.x = 0
							end
						end
		
						vec = vec + self.startVal[i + 1]
						layer.fTranslation:SetValue(frame + layer:TotalTimingOffset(), vec)
					end
	
					vec = vec + self.startVal[i + 1]
					layer.fTranslation:SetValue(frame + layer:TotalTimingOffset(), vec)
					if (self.mtFingersTouching > 1) then
						layer.fRotationZ:SetValue(frame + layer:TotalTimingOffset(), self.startAngle[i + 1] + self.mtAccumAngle + self.mtAngle)
						layer.fScale:SetValue(frame + layer:TotalTimingOffset(), self.startScale[i + 1] * self.mtAccumScale * self.mtScale)
					end
				end -- for i
			end
			
			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

	return false
end

function LM_TransformLayerModified:DrawMe(moho, view)
	if (moho.layer == nil or moho:IsPlaying()) then
		return
	end
	local layer = nil
	local bbox = nil
	local selCount = moho.document:CountSelectedLayers()
	local g = view:Graphics()
	local markerR = 4
	local matrix = LM.Matrix:new_local()
	local originWidth = 0.05
	local v = LM.Vector2:new_local()
	local vc1 = LM.ColorVector:new_local()
	local vc2 = LM.ColorVector:new_local()
	
	-- vc1:Set(MOHO.MohoGlobals.SelCol)
	vc1:Set(0, .3, 0)
	
	vc2:Set(MOHO.MohoGlobals.BackCol)
	--vc1 = (vc1 * 3 + vc2 * 4) / 7
	vc1 = (vc1 + vc2) / 2
	local col = vc1:AsColorStruct()

	if (not self.dragging) then
		for i = 0, selCount - 1 do
			--layer = moho.layer
			layer = moho.document:GetSelectedLayer(i)
			local isPrimaryLayer = (layer == moho.layer)
			bbox = self:LayerBounds(moho, layer, moho.frame, view)

			local origin = layer:Origin()

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

			g:SetSmoothing(true)
		
			g:SetColor(col)
			g:SetPenWidth(1)

			g:DrawLine(origin.x - originWidth, origin.y, origin.x + originWidth, origin.y)
			g:DrawLine(origin.x, origin.y - originWidth, origin.x, origin.y + originWidth)

			if (not isPrimaryLayer) then
				g:SetPenWidth(1)
				g:SetColor(col)
			end

			g:DrawLine(bbox.fMin.x, bbox.fMin.y, bbox.fMin.x, bbox.fMax.y)
			g:DrawLine(bbox.fMin.x, bbox.fMax.y, bbox.fMax.x, bbox.fMax.y)
			g:DrawLine(bbox.fMax.x, bbox.fMax.y, bbox.fMax.x, bbox.fMin.y)
			g:DrawLine(bbox.fMax.x, bbox.fMin.y, bbox.fMin.x, bbox.fMin.y)

			if (isPrimaryLayer) then
				g:SetPenWidth(1)
				if (selCount == 1) then
					g:SetColor(col)
				end
				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
				g:DrawLine(bbox.fMin.x - rotWidth, bbox.fMin.y - rotWidth, bbox.fMin.x - rotWidth, bbox.fMax.y + rotWidth)
				g:DrawLine(bbox.fMin.x - rotWidth, bbox.fMax.y + rotWidth, bbox.fMax.x + rotWidth, bbox.fMax.y + rotWidth)
				g:DrawLine(bbox.fMax.x + rotWidth, bbox.fMax.y + rotWidth, bbox.fMax.x + rotWidth, bbox.fMin.y - rotWidth)
				g:DrawLine(bbox.fMax.x + rotWidth, bbox.fMin.y - rotWidth, bbox.fMin.x - rotWidth, bbox.fMin.y - rotWidth)
			end

			g:SetPenWidth(1)
			if (isPrimaryLayer) then
				g:SetColor(col)
				g:SetPenWidth(2)
				local v = LM.Vector2:new_local()
			
				v:Set(bbox.fMin.x, bbox.fMin.y) g:FrameCirclePixelRadius(v, markerR)
				v:Set(bbox.fMin.x, bbox.fMax.y) g:FrameCirclePixelRadius(v, markerR)
				v:Set(bbox.fMax.x, bbox.fMax.y) g:FrameCirclePixelRadius(v, markerR)
				v:Set(bbox.fMax.x, bbox.fMin.y) g:FrameCirclePixelRadius(v, markerR)

				v:Set(bbox.fMin.x, (bbox.fMin.y + bbox.fMax.y) * 0.5) g:FrameCirclePixelRadius(v, markerR)
				v:Set(bbox.fMax.x, (bbox.fMin.y + bbox.fMax.y) * 0.5) g:FrameCirclePixelRadius(v, markerR)
				v:Set((bbox.fMin.x + bbox.fMax.x) * 0.5, bbox.fMin.y) g:FrameCirclePixelRadius(v, markerR)
				v:Set((bbox.fMin.x + bbox.fMax.x) * 0.5, bbox.fMax.y) g:FrameCirclePixelRadius(v, markerR)
			end
			g:SetSmoothing(false)
			g:SetPenWidth(1)

			g:Pop()
		end
	end


	if (not self.displayOn or selCount > 1) then
		return
	end
	if (moho.layer:PhysicsParent(-1) ~= nil) then
		return
	end

	local startFrame = moho.layer.fTranslation:GetKeyWhen(0)
	local endFrame = moho.layer.fTranslation:Duration()
	local totalTimingOffset = moho.layer:TotalTimingOffset()
	local interp = MOHO.InterpSetting:new_local()
	moho.layer.fTranslation:GetKeyInterp(endFrame, interp)
	if (interp:IsAdditiveCycle()) then
		endFrame = moho.document:EndFrame() + totalTimingOffset
	end

	if (startFrame < 0) then
		startFrame = 0
	end
	if (endFrame > startFrame) then
		local g = view:Graphics()
		local m = LM.Matrix:new_local()
		local vec = LM.Vector3:new_local()
		local oldVec = LM.Vector3:new_local()
		local origin = moho.layer:Origin()
		local splitDimensions = moho.layer.fTranslation:AreDimensionsSplit()

		g:SetColor(102, 152, 203)
		g:SetSmoothing(true)
		for frame = startFrame, endFrame do
			moho.layer:GetFullTransform(frame - totalTimingOffset, m, moho.document)
			vec:Set(origin.x, origin.y, 0)
			m:Transform(vec)
			if (frame > startFrame) then
--				g:SetColor(102, 152, 203)
				g:DrawLine(oldVec.x, oldVec.y, vec.x, vec.y)
			end
			-- color the path points before and after the current time
--			if (frame < moho.layerFrame) then
--				g:SetColor(255, 0, 0)
--			elseif (frame > moho.layerFrame) then
--				g:SetColor(0, 255, 0)
--			end
			if ((not splitDimensions) and moho.layer.fTranslation:HasKey(frame)) then
				g:DrawFatMarker(vec.x, vec.y, 5)
			else
				g:DrawMarker(vec.x, vec.y)
			end
			oldVec:Set(vec)
		end
		g:SetSmoothing(false)
	end
end

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

LM_TransformLayerModified.CHANGE_T = MOHO.MSG_BASE
LM_TransformLayerModified.CHANGE_S = MOHO.MSG_BASE + 1
LM_TransformLayerModified.CHANGE_R = MOHO.MSG_BASE + 2
LM_TransformLayerModified.RESET_T = MOHO.MSG_BASE + 3
LM_TransformLayerModified.RESET_S = MOHO.MSG_BASE + 4
LM_TransformLayerModified.RESET_R = MOHO.MSG_BASE + 5
LM_TransformLayerModified.TOGGLE_DISPLAY = MOHO.MSG_BASE + 6
LM_TransformLayerModified.FLIP_H = MOHO.MSG_BASE + 7
LM_TransformLayerModified.FLIP_V = MOHO.MSG_BASE + 8
LM_TransformLayerModified.SELECTALIGNMENT = MOHO.MSG_BASE + 9 -- through MOHO.MSG_BASE + 14

LM_TransformLayerModified.TRANSPARENCY = MOHO.MSG_BASE + 15
LM_TransformLayerModified.BLUR = MOHO.MSG_BASE + 16
LM_TransformLayerModified.TOGGLE_VISIBLE = MOHO.MSG_BASE + 17

LM_TransformLayerModified.displayOn = true

function LM_TransformLayerModified:DoLayout(moho, layout)
	layout:AddChild(LM.GUI.StaticText(MOHO.Localize("/Scripts/Tool/TransformLayer/Position=Position")))

	layout:AddChild(LM.GUI.StaticText(MOHO.Localize("/Scripts/Tool/TransformLayer/X=X:")))
	self.text_TX = LM.GUI.TextControl(0, "00.00", self.CHANGE_T, LM.GUI.FIELD_FLOAT)
	layout:AddChild(self.text_TX)

	layout:AddChild(LM.GUI.StaticText(MOHO.Localize("/Scripts/Tool/TransformLayer/Y=Y:")))
	self.text_TY = LM.GUI.TextControl(0, "00.00", self.CHANGE_T, LM.GUI.FIELD_FLOAT)
	layout:AddChild(self.text_TY)

	layout:AddChild(LM.GUI.StaticText(MOHO.Localize("/Scripts/Tool/TransformLayer/Z=Z:")))
	self.text_TZ = LM.GUI.TextControl(0, "00.00", self.CHANGE_T, LM.GUI.FIELD_FLOAT)
	layout:AddChild(self.text_TZ)
	
	layout:AddChild(LM.GUI.Button(MOHO.Localize("/Scripts/Tool/TransformLayer/Reset=Reset"), self.RESET_T))

	-- *****************************

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

	layout:AddChild(LM.GUI.StaticText(MOHO.Localize("/Scripts/Tool/TransformLayer/X=X:")))
	self.text_SX = LM.GUI.TextControl(0, "00.00", self.CHANGE_S, LM.GUI.FIELD_FLOAT)
	layout:AddChild(self.text_SX)

	layout:AddChild(LM.GUI.StaticText(MOHO.Localize("/Scripts/Tool/TransformLayer/Y=Y:")))
	self.text_SY = LM.GUI.TextControl(0, "00.00", self.CHANGE_S, LM.GUI.FIELD_FLOAT)
	layout:AddChild(self.text_SY)

	layout:AddChild(LM.GUI.StaticText(MOHO.Localize("/Scripts/Tool/TransformLayer/Z=Z:")))
	self.text_SZ = LM.GUI.TextControl(0, "00.00", self.CHANGE_S, LM.GUI.FIELD_FLOAT)
	layout:AddChild(self.text_SZ)
	
	layout:AddChild(LM.GUI.Button(MOHO.Localize("/Scripts/Tool/TransformLayer/Reset=Reset"), self.RESET_S))

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

	self.text_R = LM.GUI.TextControl(0, "000.00", self.CHANGE_R, LM.GUI.FIELD_FLOAT)
	self.text_R:SetWheelInc(1)
	layout:AddChild(self.text_R)
	
	layout:AddChild(LM.GUI.Button(MOHO.Localize("/Scripts/Tool/TransformLayer/Reset=Reset"), self.RESET_R))
	
	-- *****************************

	self.displayCheck = LM.GUI.CheckBox(MOHO.Localize("/Scripts/Tool/TransformLayer/ShowPath=Show path"), self.TOGGLE_DISPLAY)
	layout:AddChild(self.displayCheck)

	layout:AddChild(LM.GUI.ImageButton("ScriptResources/flip_layer_h", MOHO.Localize("/Scripts/Tool/SetOrigin/FlipH=Flip Layer Horizontally"), false, self.FLIP_H, true))
	layout:AddChild(LM.GUI.ImageButton("ScriptResources/flip_layer_v", MOHO.Localize("/Scripts/Tool/SetOrigin/FlipV=Flip Layer Vertically"), false, self.FLIP_V, true))
	
	-- *****************************
	
	self.menu = LM.GUI.Menu(MOHO.Localize("/Scripts/Tool/TransformLayer/Align=Align"))
	self.menu:AddItem(MOHO.Localize("/Scripts/Tool/Left=Left"), 0,  LM_TransformLayerModified.SELECTALIGNMENT)
	self.menu:AddItem(MOHO.Localize("/Scripts/Tool/Right=Right"), 0, LM_TransformLayerModified.SELECTALIGNMENT + 1)
	self.menu:AddItem(MOHO.Localize("/Scripts/Tool/CenterHorizontally=Center Horizontally"), 0, LM_TransformLayerModified.SELECTALIGNMENT + 2)
	self.menu:AddItem(MOHO.Localize("/Scripts/Tool/Top=Top"), 0, LM_TransformLayerModified.SELECTALIGNMENT + 3)
	self.menu:AddItem(MOHO.Localize("/Scripts/Tool/Bottom=Bottom"), 0, LM_TransformLayerModified.SELECTALIGNMENT + 4)
	self.menu:AddItem(MOHO.Localize("/Scripts/Tool/CenterVertically=Center Vertically"), 0 , LM_TransformLayerModified.SELECTALIGNMENT + 5)
	self.popup = LM.GUI.ImagePopupMenu("ScriptResources/align_layers", false, true)
	self.popup:SetMenu(self.menu)
	self.popup:SetToolTip(MOHO.Localize("/Scripts/Tool/TransformLayer/AlignLayers=Align Layers"))
	layout:AddChild(self.popup)
	
	-- Custom controls:
	-- layout:AddChild(LM.GUI.StaticText("Opacity:"))
	self.transP = LM.GUI.TextControl(0, "000", self.TRANSPARENCY, LM.GUI.FIELD_UINT, "Opacity:")
	layout:AddChild(self.transP)
	
	-- layout:AddChild(LM.GUI.StaticText("Blur:"))
	self.blurred = LM.GUI.TextControl(0, "000", self.BLUR, LM.GUI.FIELD_UFLOAT, "Blur:")
	layout:AddChild(self.blurred)

	self.isVisible = LM.GUI.CheckBox("Visible", self.TOGGLE_VISIBLE)
	layout:AddChild(self.isVisible)
	
end

function LM_TransformLayerModified:UpdateWidgets(moho)
	local enableAlignmentPopup = moho.document:CountSelectedLayers() > 1

	self.text_TX:SetValue(moho.layer.fTranslation.value.x)
	self.text_TY:SetValue(moho.layer.fTranslation.value.y)
	self.text_TZ:SetValue(moho.layer.fTranslation.value.z)

	self.text_SX:SetValue(moho.layer.fScale.value.x)
	self.text_SY:SetValue(moho.layer.fScale.value.y)
	self.text_SZ:SetValue(moho.layer.fScale.value.z)

	self.text_R:SetValue(math.deg(moho.layer.fRotationZ.value))

	self.displayCheck:SetValue(self.displayOn)
	
	self.menu:UncheckAll()
	for i = 0, 5 do
		self.menu:SetEnabled(LM_TransformLayerModified.SELECTALIGNMENT + i, enableAlignmentPopup)
	end
	self.popup:Redraw()
	
	local transValue = moho.layer.fAlpha.value * 100
	transValue = LM.Clamp(transValue, 0, 100)
	self.transP:SetValue(transValue)
	self.blurred:SetValue(moho.layer.fBlur.value * moho.document:Height())
	self.isVisible:SetValue(moho.layer.fVisibility.value)
	
end

function LM_TransformLayerModified:HandleMessage(moho, view, msg)
	local newVal = LM.Vector3:new_local()
	local selCount = moho.document:CountSelectedLayers()

	if (msg == self.RESET_T) then
		moho.document:PrepMultiUndo(true)
		moho.document:SetDirty()

		for i = 0, selCount - 1 do
			local layer = moho.document:GetSelectedLayer(i)
			local timingOffset = layer:TotalTimingOffset()
			if (moho.frame == 0) then
				newVal:Set(0, 0, 0)
				layer.fTranslation:SetValue(0, newVal)
			else
				newVal:Set(layer.fTranslation:GetValue(0))
				layer.fTranslation:SetValue(moho.frame + timingOffset, newVal)
			end
		end
		self:UpdateWidgets(moho)
		moho:NewKeyframe(CHANNEL_LAYER_T)
		moho.document:DepthSort()
		moho:UpdateUI()
	elseif (msg == self.RESET_S) then
		moho.document:PrepMultiUndo(true)
		moho.document:SetDirty()

		for i = 0, selCount - 1 do
			local layer = moho.document:GetSelectedLayer(i)
			local timingOffset = layer:TotalTimingOffset()
			if (moho.frame == 0) then
				newVal:Set(1, 1, 1)
				layer.fScale:SetValue(0, newVal)
			else
				newVal:Set(layer.fScale:GetValue(0))
				layer.fScale:SetValue(moho.frame + timingOffset, newVal)
			end
		end
		self:UpdateWidgets(moho)
		moho:NewKeyframe(CHANNEL_LAYER_S)
		moho.document:DepthSort()
		moho:UpdateUI()
	elseif (msg == self.RESET_R) then
		moho.document:PrepMultiUndo(true)
		moho.document:SetDirty()

		for i = 0, selCount - 1 do
			local layer = moho.document:GetSelectedLayer(i)
			local timingOffset = layer:TotalTimingOffset()
			if (moho.frame == 0) then
				layer.fRotationZ:SetValue(0, 0.0)
			else
				layer.fRotationZ:SetValue(moho.frame + timingOffset, layer.fRotationZ:GetValue(0))
			end
		end
		self:UpdateWidgets(moho)
		moho:NewKeyframe(CHANNEL_LAYER_ROT_Z)
		moho.document:DepthSort()
		moho:UpdateUI()
	elseif (msg == self.CHANGE_T) then
		moho.document:PrepMultiUndo(true)
		moho.document:SetDirty()

		newVal.x = self.text_TX:FloatValue()
		newVal.y = self.text_TY:FloatValue()
		newVal.z = self.text_TZ:FloatValue()
		local offset = newVal - moho.layer.fTranslation:GetValue(moho.frame + moho.layer:TotalTimingOffset())
		for i = 0, selCount - 1 do
			local layer = moho.document:GetSelectedLayer(i)
			local timingOffset = layer:TotalTimingOffset()
			if (offset:Mag() > 0.0001) then
				layer.fTranslation:SetValue(moho.frame + layer:TotalTimingOffset(), newVal)
				for j = 0, layer.fTranslation:CountKeys() - 1 do
					if (layer.fTranslation:IsKeySelectedByID(j) and layer.fTranslation:GetKeyWhen(j) ~= moho.frame + timingOffset) then
						local vec = layer.fTranslation:GetValueByID(j) + offset
						layer.fTranslation:SetValueByID(j, vec)
					end
				end
				moho:NewKeyframe(CHANNEL_LAYER_T)
			end
		end
		moho.document:DepthSort()
		moho:UpdateUI()
	elseif (msg == self.CHANGE_S) then
		moho.document:PrepMultiUndo(true)
		moho.document:SetDirty()

		newVal.x = self.text_SX:FloatValue()
		newVal.y = self.text_SY:FloatValue()
		newVal.z = self.text_SZ:FloatValue()
		local offset = moho.layer.fScale:GetValue(moho.frame + moho.layer:TotalTimingOffset())
		offset.x = newVal.x / offset.x
		offset.y = newVal.y / offset.y
		offset.z = newVal.z / offset.z
		for i = 0, selCount - 1 do
			local layer = moho.document:GetSelectedLayer(i)
			local timingOffset = layer:TotalTimingOffset()
			local vec = moho.layer.fScale:GetValue(moho.frame + moho.layer:TotalTimingOffset()) - newVal
			if (vec:Mag() > 0.0001) then
				layer.fScale:SetValue(moho.frame + timingOffset, newVal)
				for j = 0, layer.fScale:CountKeys() - 1 do
					if (layer.fScale:IsKeySelectedByID(j) and layer.fScale:GetKeyWhen(j) ~= moho.frame + timingOffset) then
						local scale = layer.fScale:GetValueByID(j)
						scale.x = scale.x * offset.x
						scale.y = scale.y * offset.y
						scale.z = scale.z * offset.z
						layer.fScale:SetValueByID(j, scale)
					end
				end
				moho:NewKeyframe(CHANNEL_LAYER_S)
			end
		end
		moho:UpdateUI()
	elseif (msg == self.CHANGE_R) then
		moho.document:PrepMultiUndo(true)
		moho.document:SetDirty()

		newVal = math.rad(self.text_R:FloatValue())
		local d = newVal - moho.layer.fRotationZ:GetValue(moho.frame + moho.layer:TotalTimingOffset())
		for i = 0, selCount - 1 do
			local layer = moho.document:GetSelectedLayer(i)
			local timingOffset = layer:TotalTimingOffset()
			if (math.abs(d) > 0.0001) then
				layer.fRotationZ:SetValue(moho.frame + timingOffset, newVal)
				for j = 0, layer.fRotationZ:CountKeys() - 1 do
					if (layer.fRotationZ:IsKeySelectedByID(j) and layer.fRotationZ:GetKeyWhen(j) ~= moho.frame + timingOffset) then
						local angle = layer.fRotationZ:GetValueByID(j) + d
						layer.fRotationZ:SetValueByID(j, angle)
					end
				end
				moho:NewKeyframe(CHANNEL_LAYER_ROT_Z)
			end
		end
	elseif (msg == self.TOGGLE_DISPLAY) then
		self.displayOn = self.displayCheck:Value()
	elseif (msg == self.FLIP_H) then
		moho.document:PrepMultiUndo(true)
		moho.document:SetDirty()
		for i = 0, selCount - 1 do
			local layer = moho.document:GetSelectedLayer(i)
			layer.fFlipH:SetValue(moho.frame + layer:TotalTimingOffset(), not layer.fFlipH.value)
			moho:NewKeyframe(CHANNEL_LAYER_FLIP_H)
		end
	elseif (msg == self.FLIP_V) then
		moho.document:PrepMultiUndo(true)
		moho.document:SetDirty()
		for i = 0, selCount - 1 do
			local layer = moho.document:GetSelectedLayer(i)
			layer.fFlipV:SetValue(moho.frame + layer:TotalTimingOffset(), not layer.fFlipV.value)
			moho:NewKeyframe(CHANNEL_LAYER_FLIP_V)
		end
	elseif (msg >= LM_TransformLayerModified.SELECTALIGNMENT and msg <= LM_TransformLayerModified.SELECTALIGNMENT + 5) then
		moho:AlignLayers(msg - LM_TransformLayerModified.SELECTALIGNMENT)
	elseif (msg == self.TOGGLE_VISIBLE) then
		moho.document:PrepMultiUndo()
		moho.document:SetDirty()
		for i = 0, selCount - 1 do
			local layer = moho.document:GetSelectedLayer(i)
			layer.fVisibility:SetValue(moho.frame + layer:TotalTimingOffset(), not layer.fVisibility.value)
			moho:NewKeyframe(CHANNEL_LAYER_VIS)
			moho:UpdateUI()
		end
	elseif (msg == self.TRANSPARENCY) then
		local alphaValue = 0.0
		if self.transP == 0 then
			alphaValue = 0.0
		else
			alphaValue = self.transP:IntValue()/100
		end
		moho.document:PrepMultiUndo()
		moho.document:SetDirty()
		for i = 0, selCount - 1 do
			local layer = moho.document:GetSelectedLayer(i)
			layer.fAlpha:SetValue(moho.frame + layer:TotalTimingOffset(), alphaValue)
			moho:NewKeyframe(CHANNEL_LAYER_ALPHA)
			moho:UpdateUI()
		end
	elseif (msg == self.BLUR) then
		local blurValue = 0.0
		if self.blurred == 0 then
			blurValue = 0.0
		else
			blurValue = self.blurred:FloatValue()/moho.document:Height()
		end
		moho.document:PrepMultiUndo()
		moho.document:SetDirty()
		for i = 0, selCount - 1 do
			local layer = moho.document:GetSelectedLayer(i)
			layer.fBlur:SetValue(moho.frame + layer:TotalTimingOffset(), blurValue)
			moho:NewKeyframe(CHANNEL_LAYER_BLUR)
			moho:UpdateUI()
		end
	end
end

Icon
Modified LM Transform Layer
Listed

Author: Stan View Script
Script type: Tool

Uploaded: Dec 24 2020, 09:29

Last modified: Jan 19 2021, 07:28

Added opacity, visibility and blur controls, and also the bounding box color is changed to green
Image
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: 965