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

ScriptName = "MR_Path"

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

MR_Path = {}

function MR_Path:Name()
	return self:Localize('UILabel')
end

function MR_Path:Version()
	return '1.2'
end

function MR_Path:UILabel()
	return self:Localize('UILabel')
end

function MR_Path:Creator()
	return 'Eugene Babich'
end

function MR_Path:Description()
	return self:Localize('Description')
end

-- **************************************************
-- Is Relevant / Is Enabled
-- **************************************************

function MR_Path:IsRelevant(moho)
	return true
end

function MR_Path:IsEnabled(moho)
	return true
end

-- **************************************************
-- Recurring Values
-- **************************************************

MR_Path.pathOpacity = 100
MR_Path.width = 1
MR_Path.pathColorR = 84
MR_Path.pathColorG = 212
MR_Path.pathColorB = 196
MR_Path.pathColorA = 255
MR_Path.centerLine = false
MR_Path.drawTimingMark = true
MR_Path.timingMarksSize = 0.5
MR_Path.trackBoneTip = false
MR_Path.adjacentFramesRange = false
MR_Path.adjacentFrames = 10
MR_Path.playbackRange = true
MR_Path.fromToRange = false
MR_Path.from = 1
MR_Path.to = 20
MR_Path.interval = 1
MR_Path.maxTargets = 60
MR_Path.isMouseDragging = false
MR_Path.selRect = LM.Rect:new_local()
MR_Path.trackingList = {}
MR_Path.trackingList.target = {}
MR_Path.trackingList.targetType = {}
MR_Path.trackingList.targetID = {}
MR_Path.trackingList.targetName = {}
MR_Path.trackingList.layerName = {}
MR_Path.trackingList.layerUUID = {}
MR_Path.trackingList.layer = {}
MR_Path.trackingList.boneTip = {}
MR_Path.trackingList.color = {}
MR_Path.trackingList.description = {}
MR_Path.trackingList.pos = {}
MR_Path.isTracking = false

-- **************************************************
-- Prefs
-- **************************************************

function MR_Path:LoadPrefs(prefs)
	self.pathOpacity = prefs:GetInt("MR_Path.pathOpacity", 100)
	self.width = prefs:GetFloat("MR_Path.width", 1)
	self.pathColorR = prefs:GetInt("MR_Path.pathColorR", 84)
	self.pathColorG = prefs:GetInt("MR_Path.pathColorG", 212)
	self.pathColorB = prefs:GetInt("MR_Path.pathColorB", 196)
	self.pathColorA = prefs:GetInt("MR_Path.pathColorA", 255)
	self.centerLine = prefs:GetBool("MR_Path.centerLine", false)
	self.drawTimingMark = prefs:GetBool("MR_Path.drawTimingMark", true)
	self.timingMarksSize = prefs:GetFloat("MR_Path.timingMarksSize", 0.5)
	self.trackBoneTip = prefs:GetBool("MR_Path.trackBoneTip", false)
	self.adjacentFramesRange = prefs:GetBool("MR_Path.adjacentFramesRange", false)
	self.adjacentFrames = prefs:GetInt("MR_Path.adjacentFrames", 10)
	self.playbackRange = prefs:GetBool("MR_Path.playbackRange", true)
	self.fromToRange = prefs:GetBool("MR_Path.fromToRange", false)
	self.from = prefs:GetInt("MR_Path.from", 1)
	self.to = prefs:GetInt("MR_Path.to", 20)
	self.interval = prefs:GetInt("MR_Path.interval", 1)
end

function MR_Path:SavePrefs(prefs)
	prefs:SetInt("MR_Path.pathOpacity", self.pathOpacity)
	prefs:SetFloat("MR_Path.width", self.width)
	prefs:SetInt("MR_Path.pathColorR", self.pathColorR)
	prefs:SetInt("MR_Path.pathColorG", self.pathColorG)
	prefs:SetInt("MR_Path.pathColorB", self.pathColorB)
	prefs:SetInt("MR_Path.pathColorA", self.pathColorA)
	prefs:SetBool("MR_Path.centerLine", self.centerLine)
	prefs:SetBool("MR_Path.drawTimingMark", self.drawTimingMark)
	prefs:SetFloat("MR_Path.timingMarksSize", self.timingMarksSize)
	prefs:SetBool("MR_Path.trackBoneTip", self.trackBoneTip)
	prefs:SetBool("MR_Path.adjacentFramesRange", self.adjacentFramesRange)
	prefs:SetInt("MR_Path.adjacentFrames", self.adjacentFrames)
	prefs:SetBool("MR_Path.playbackRange", self.playbackRange)
	prefs:SetBool("MR_Path.fromToRange", self.fromToRange)
	prefs:SetInt("MR_Path.from", self.from)
	prefs:SetInt("MR_Path.to", self.to)
	prefs:SetInt("MR_Path.interval", self.interval)
end

function MR_Path:ResetPrefs()
	self.pathOpacity = 100
	self.width = 1
	self.pathColorR = 84
	self.pathColorG = 212
	self.pathColorB = 196
	self.pathColorA = 255
	self.centerLine = false
	self.drawTimingMark = true
	self.timingMarksSize = 0.5
	self.trackBoneTip = false
	self.adjacentFramesRange = false
	self.adjacentFrames = 10
	self.playbackRange = true
	self.fromToRange = false
	self.from = 1
	self.to = 20
	self.interval = 1
end

-- **************************************************
-- Keyboard/Mouse Control
-- **************************************************

function MR_Path:OnMouseDown(moho, mouseEvent)
	local skel = moho:Skeleton()
	local mesh = moho:Mesh()
	if not skel and not mesh then
		return
	end
	
	self.isMouseDragging = true
	if not mouseEvent.shiftKey and not mouseEvent.altKey then
		if skel then
			skel:SelectNone()
		elseif mesh then
			mesh:SelectNone()
		end	
	end
	
	self.selRect.left = mouseEvent.startPt.x
	self.selRect.top = mouseEvent.startPt.y
	self.selRect.right = mouseEvent.pt.x
	self.selRect.bottom = mouseEvent.pt.y
	mouseEvent.view:Graphics():SelectionRect(self.selRect)
	mouseEvent.view:DrawMe()
end

function MR_Path:OnMouseMoved(moho, mouseEvent)
	local skel = moho:Skeleton()
	local mesh = moho:Mesh()
	if not skel and not mesh then
		return
	end

	mouseEvent.view:Graphics():SelectionRect(self.selRect)
	self.selRect.right = mouseEvent.pt.x
	self.selRect.bottom = mouseEvent.pt.y
	mouseEvent.view:Graphics():SelectionRect(self.selRect)
	mouseEvent.view:RefreshView()
	mouseEvent.view:DrawMe()
end

function MR_Path:OnMouseUp(moho, mouseEvent)
	local skel = moho:Skeleton()
	local mesh = moho:Mesh()
	local mouseDist = math.abs(mouseEvent.pt.x - mouseEvent.startPt.x) + math.abs(mouseEvent.pt.y - mouseEvent.startPt.y)

	local id
	
	if skel then
		id = mouseEvent.view:PickBone(mouseEvent.pt, mouseEvent.vec, moho.layer, true)
		if not mouseEvent.shiftKey and not mouseEvent.altKey then
			skel:SelectNone()
		end	
		if id ~= -1 then
			skel:Bone(id).fSelected = not mouseEvent.altKey
		end
	elseif mesh then
		id = mouseEvent.view:PickPoint(mouseEvent.pt)
		if not mouseEvent.shiftKey and not mouseEvent.altKey then
			mesh:SelectNone()
		end	
		if id ~= -1 then
			mesh:Point(id).fSelected = not mouseEvent.altKey
		end
	end

	self.isMouseDragging = false

	local v = LM.Vector2:new_local()
	local screenPt = LM.Point:new_local()
	local m = LM.Matrix:new_local()

	if	moho.layer:LayerType() == MOHO.LT_BONE then
		if (skel ~= nil) then
			self.selRect:Normalize()
			moho.layer:GetFullTransform(moho.frame, m, moho.document)
			for i = 0, skel:CountBones() - 1 do
				local bone = skel:Bone(i)
				local boneMatrix = bone.fMovedMatrix
				for j = 0, 10 do
					v:Set(bone.fLength * j / 10.0, 0)
					boneMatrix:Transform(v)
					m:Transform(v)
					mouseEvent.view:Graphics():WorldToScreen(v, screenPt)
					if (self.selRect:Contains(screenPt)) then
						if (mouseEvent.altKey) then
							bone.fSelected = false
						else
							bone.fSelected = true
						end
						break
					end
				end
			end
		end
	elseif	moho.layer:LayerType() == MOHO.LT_VECTOR then
		if (mesh ~= nil) then
			for i = 0, mesh:CountPoints() - 1 do
				local v = LM.Vector2:new_local()
				local screenPt = LM.Point:new_local()
				local m = LM.Matrix:new_local()

				self.selRect:Normalize()
				moho.drawingLayer:GetFullTransform(moho.frame, m, moho.document)
				for i = 0, mesh:CountPoints() - 1 do
					local pt = mesh:Point(i)
					if (not pt.fHidden) then
						v:Set(pt.fPos)
						m:Transform(v)
						mouseEvent.view:Graphics():WorldToScreen(v, screenPt)
						if (self.selRect:Contains(screenPt)) then
							if (mouseEvent.altKey) then
								pt.fSelected = false
							else
								pt.fSelected = true
							end
						end
					end
				end
				self.isMouseDragging = false
			end
		end	
	end
	moho:UpdateSelectedChannels()
end

function MR_Path:DrawMe(moho, view)
	if self.isMouseDragging then
		local g = view:Graphics()
		g:SelectionRect(self.selRect)
	end
end

local MR_PathFloatingPanelDialog = {}

MR_PathFloatingPanelDialog.ADD_TO_TRACKING = MOHO.MSG_BASE
MR_PathFloatingPanelDialog.REMOVE_FROM_TRACKING = MOHO.MSG_BASE + 1
MR_PathFloatingPanelDialog.CLEAR_TRECKING_LIST = MOHO.MSG_BASE + 2
MR_PathFloatingPanelDialog.CREATE_PATH = MOHO.MSG_BASE + 3
MR_PathFloatingPanelDialog.CLEAR_PATH = MOHO.MSG_BASE + 4
MR_PathFloatingPanelDialog.DELETE_PATH_LAYER = MOHO.MSG_BASE + 5
MR_PathFloatingPanelDialog.SHOW_HIDE_PATH = MOHO.MSG_BASE + 6
MR_PathFloatingPanelDialog.PATH_OPACITY = MOHO.MSG_BASE + 7
MR_PathFloatingPanelDialog.WIDTH = MOHO.MSG_BASE + 8
MR_PathFloatingPanelDialog.COLOR = MOHO.MSG_BASE + 9
MR_PathFloatingPanelDialog.DRAW_CENTER_LINE = MOHO.MSG_BASE + 10
MR_PathFloatingPanelDialog.DRAW_TIMING_MARK = MOHO.MSG_BASE + 11
MR_PathFloatingPanelDialog.TIMING_MARKS_SIZE = MOHO.MSG_BASE + 12
MR_PathFloatingPanelDialog.TRACK_BONE_TIP = MOHO.MSG_BASE + 13
MR_PathFloatingPanelDialog.ADJACENT_FRAMES_RANGE = MOHO.MSG_BASE + 14
MR_PathFloatingPanelDialog.ADJACENT_FRAMES = MOHO.MSG_BASE + 15
MR_PathFloatingPanelDialog.PLAYBACK_RANGE = MOHO.MSG_BASE + 16
MR_PathFloatingPanelDialog.FROM_TO_RANGE = MOHO.MSG_BASE + 17
MR_PathFloatingPanelDialog.FROM = MOHO.MSG_BASE + 18
MR_PathFloatingPanelDialog.TO = MOHO.MSG_BASE + 19
MR_PathFloatingPanelDialog.INTERVAL_1 = MOHO.MSG_BASE + 20
MR_PathFloatingPanelDialog.INTERVAL_2 = MOHO.MSG_BASE + 21
MR_PathFloatingPanelDialog.PATH_TARGET_MANAGER = MOHO.MSG_BASE + 22

function MR_PathFloatingPanelDialog:new(moho)
    local d = LM.GUI.SimpleDialog(MR_Path:Localize('UILabel'), MR_PathFloatingPanelDialog)
    local l = d:GetLayout()
	
	l:PushH()
	
		l:AddPadding(7)
	
		d.addToTrackingButton = LM.GUI.ImageButton('ScriptResources/mr_path/mr_add_to_tracking', MR_Path:Localize('Add to tracking'), false, self.ADD_TO_TRACKING, false)
		l:AddChild(d.addToTrackingButton, LM.GUI.ALIGN_LEFT, 0)

		d.removeFromTrackingButton = LM.GUI.ImageButton('ScriptResources/mr_path/mr_remove_from_tracking', MR_Path:Localize('Remove from tracking'), false, self.REMOVE_FROM_TRACKING, false)
		l:AddChild(d.removeFromTrackingButton, LM.GUI.ALIGN_LEFT, 0)

		d.clearTrackingListButton = LM.GUI.ImageButton('ScriptResources/mr_path/mr_clear_tracking_list', MR_Path:Localize('Clear tracking list'), false, self.CLEAR_TRECKING_LIST, false)
		l:AddChild(d.clearTrackingListButton, LM.GUI.ALIGN_LEFT, 0)
		
		l:AddChild(LM.GUI.Divider(true), LM.GUI.ALIGN_FILL)

		d.createPathButton = LM.GUI.ImageButton('ScriptResources/mr_path/mr_create_path', MR_Path:Localize('Create path'), false, self.CREATE_PATH, false)
		l:AddChild(d.createPathButton, LM.GUI.ALIGN_LEFT, 0)

		d.clearPathButton = LM.GUI.ImageButton('ScriptResources/mr_path/mr_clear_path', MR_Path:Localize('Clear path'), false, self.CLEAR_PATH, false)
		l:AddChild(d.clearPathButton, LM.GUI.ALIGN_LEFT, 0)

		d.deletePathLayerButton = LM.GUI.ImageButton('ScriptResources/mr_path/mr_delete_path_layer', MR_Path:Localize('Delete path layer'), false, self.DELETE_PATH_LAYER, false)
		l:AddChild(d.deletePathLayerButton, LM.GUI.ALIGN_LEFT, 0)
		
		l:AddChild(LM.GUI.Divider(true), LM.GUI.ALIGN_FILL)

		d.showHidePathCheck = LM.GUI.ImageButton('ScriptResources/mr_path/mr_visibility', MR_Path:Localize('Show hide'), true, self.SHOW_HIDE_PATH, false)
		l:AddChild(d.showHidePathCheck, LM.GUI.ALIGN_LEFT, 0)

		d.pathOpacityInput = LM.GUI.TextControl(0, '100', self.PATH_OPACITY, LM.GUI.FIELD_INT, MR_Path:Localize('Path Opacity:'))
		d.pathOpacityInput:SetToolTip(MR_Path:Localize('Opacity for all Paths'))
		l:AddChild(d.pathOpacityInput, LM.GUI.ALIGN_LEFT, 0)

		d.widthInput = LM.GUI.TextControl(0, '100', self.WIDTH, LM.GUI.FIELD_UFLOAT, MR_Path:Localize('Width:'))
		d.widthInput:SetWheelInc(1.0)
		d.widthInput:SetWheelInteger(true)
		d.widthInput:SetToolTip(MR_Path:Localize('Width for all Paths'))
		l:AddChild(d.widthInput, LM.GUI.ALIGN_LEFT, 0)
		
		d.colorText = LM.GUI.DynamicText(MR_Path:Localize('Color'), 0)
		l:AddChild(d.colorText, LM.GUI.ALIGN_LEFT, 0)
		
		d.pathColorSwatch = LM.GUI.ShortColorSwatch(true, self.COLOR)
		l:AddChild(d.pathColorSwatch, LM.GUI.ALIGN_LEFT)
		d.pathColorSwatch:SetToolTip(MR_Path:Localize('Path Color Tooltip'))
		
		l:AddChild(LM.GUI.Divider(true), LM.GUI.ALIGN_FILL)
		
		d.centerLineCheck = LM.GUI.ImageButton('ScriptResources/mr_path/mr_center_line', MR_Path:Localize('Draw center line'), true, self.DRAW_CENTER_LINE, false)
		l:AddChild(d.centerLineCheck, LM.GUI.ALIGN_LEFT, 0)
		
		d.drawTimingMarkCheck = LM.GUI.ImageButton('ScriptResources/mr_path/mr_draw_timing_marks', MR_Path:Localize('Draw timing marks'), true, self.DRAW_TIMING_MARK, false)
		l:AddChild(d.drawTimingMarkCheck, LM.GUI.ALIGN_LEFT, 0)
		
	l:Pop()
	
	l:AddChild(LM.GUI.Divider(false), LM.GUI.ALIGN_FILL)
	
	-- l:AddChild(LM.GUI.Divider(true), LM.GUI.ALIGN_FILL)
	
	l:PushH()
		
		d.timingMarksSizeInput = LM.GUI.TextControl(0, '100', self.TIMING_MARKS_SIZE, LM.GUI.FIELD_FLOAT, MR_Path:Localize('Size:'))
		d.timingMarksSizeInput:SetWheelInc(0.01)
		d.timingMarksSizeInput:SetToolTip(MR_Path:Localize('Timing marks size'))
		l:AddChild(d.timingMarksSizeInput, LM.GUI.ALIGN_LEFT, 0)
		
		d.trackBoneTipCheck = LM.GUI.ImageButton('ScriptResources/mr_path/mr_track_bone_tip', MR_Path:Localize('Track bone tip'), true, self.TRACK_BONE_TIP, false)
		l:AddChild(d.trackBoneTipCheck, LM.GUI.ALIGN_LEFT, 0)
		
		l:AddChild(LM.GUI.Divider(true), LM.GUI.ALIGN_FILL)
		
		d.adjacentFramesRangeCheck = LM.GUI.ImageButton('ScriptResources/mr_path/mr_adjacent_frames', MR_Path:Localize('Adjacent frames range'), true, self.ADJACENT_FRAMES_RANGE, false)
		l:AddChild(d.adjacentFramesRangeCheck, LM.GUI.ALIGN_LEFT, 0)
		
		d.adjacentFramesInput = LM.GUI.TextControl(0, '100', self.ADJACENT_FRAMES, LM.GUI.FIELD_INT, '')
		d.adjacentFramesInput:SetToolTip(MR_Path:Localize('Adjacent frames'))
		l:AddChild(d.adjacentFramesInput, LM.GUI.ALIGN_LEFT, 0)
		
		l:AddChild(LM.GUI.Divider(true), LM.GUI.ALIGN_FILL)
		
		d.playbackRangeCheck = LM.GUI.ImageButton('ScriptResources/mr_path/mr_playback_range', MR_Path:Localize('Playback range'), true, self.PLAYBACK_RANGE, false)
		l:AddChild(d.playbackRangeCheck, LM.GUI.ALIGN_LEFT, 0)
		
		l:AddChild(LM.GUI.Divider(true), LM.GUI.ALIGN_FILL)
		
		d.fromToRangeCheck = LM.GUI.ImageButton('ScriptResources/mr_path/mr_from_to', MR_Path:Localize('From to range'), true, self.FROM_TO_RANGE, false)
		l:AddChild(d.fromToRangeCheck, LM.GUI.ALIGN_LEFT, 0)
		
		d.fromInput = LM.GUI.TextControl(0, '100', self.FROM, LM.GUI.FIELD_INT, '')
		d.fromInput:SetToolTip(MR_Path:Localize('From'))
		l:AddChild(d.fromInput, LM.GUI.ALIGN_LEFT, 0)
		
		d.toInput = LM.GUI.TextControl(0, '100', self.TO, LM.GUI.FIELD_INT, '')
		d.toInput:SetToolTip(MR_Path:Localize('To'))
		l:AddChild(d.toInput, LM.GUI.ALIGN_LEFT, 0)
		
		l:AddChild(LM.GUI.Divider(true), LM.GUI.ALIGN_FILL)
		
		l:AddChild(LM.GUI.StaticText(MR_Path:Localize("IntervalText")))

		d.intervalMenu = LM.GUI.Menu(MOHO.Localize("Interval=Interval"))
		d.intervalMenu:AddItem(MOHO.Localize("1=1"), 0, self.INTERVAL_1)
		d.intervalMenu:AddItemAlphabetically(MOHO.Localize("2=2"), 0, self.INTERVAL_2)

		d.intervalPopup = LM.GUI.PopupMenu(50, true)
		d.intervalPopup:SetMenu(d.intervalMenu)
		l:AddChild(d.intervalPopup)
		
		l:AddChild(LM.GUI.Divider(true), LM.GUI.ALIGN_FILL)
		
		d.PathTargetManagerButton = LM.GUI.Button(MR_Path:Localize('Path Target Manager'), self.PATH_TARGET_MANAGER)
		l:AddChild(d.PathTargetManagerButton, LM.GUI.ALIGN_LEFT, 0)
	
	l:Pop()
	
	return d
end

function MR_PathFloatingPanelDialog:UpdateWidgets(moho)
	if MR_Path.isTracking then
		return
	end
	
	local helper = MOHO.ScriptInterfaceHelper:new_local()
	local moho = helper:MohoObject()

	local pathLayer, targetLayer = MR_Path:CheckExistPathLayer(moho)
	
	helper:delete()
	
	local isPathLayer = false
	if pathLayer then
		isPathLayer = true
	end
	
	local isTarget = false
	if MR_Path.trackingList.targetID[1] then
		isTarget = true
	end
	
	self.createPathButton:Enable(isTarget)
	self.clearPathButton:Enable(isPathLayer)
	self.deletePathLayerButton:Enable(isPathLayer)
	self.showHidePathCheck:Enable(isPathLayer)
	if pathLayer then
		self.showHidePathCheck:SetValue(pathLayer:IsVisible())
		pathLayer.fAlpha:GetValue(0)
	else
		self.pathOpacityInput:SetValue(MR_Path.pathOpacity)
		self.showHidePathCheck:SetValue(true)
	end
	self.widthInput:SetValue(MR_Path.width)
	local pathColor = LM.rgb_color:new_local()
	pathColor.r = MR_Path.pathColorR
	pathColor.g = MR_Path.pathColorG
	pathColor.b = MR_Path.pathColorB
	pathColor.a = MR_Path.pathColorA
	self.pathColorSwatch:SetValue(pathColor)
	self.centerLineCheck:SetValue(MR_Path.centerLine)
	self.drawTimingMarkCheck:SetValue(MR_Path.drawTimingMark)
	self.timingMarksSizeInput:SetValue(MR_Path.timingMarksSize)
	self.trackBoneTipCheck:SetValue(MR_Path.trackBoneTip)
	self.adjacentFramesRangeCheck:SetValue(MR_Path.adjacentFramesRange)
	self.adjacentFramesInput:SetValue(MR_Path.adjacentFrames)
	self.adjacentFramesInput:Enable(MR_Path.adjacentFramesRange)
	self.playbackRangeCheck:SetValue(MR_Path.playbackRange)
	self.fromToRangeCheck:SetValue(MR_Path.fromToRange)
	self.fromInput:SetValue(MR_Path.from)
	self.fromInput:Enable(MR_Path.fromToRange)
	self.toInput:SetValue(MR_Path.to)
	self.toInput:Enable(MR_Path.fromToRange)
	
	self.intervalMenu:SetChecked(MR_Path.INTERVAL_1, false)
	self.intervalMenu:SetChecked(MR_Path.INTERVAL_2, false)
	if (MR_Path.interval == 1) then
		self.intervalMenu:SetChecked(MR_Path.INTERVAL_1, true)
	elseif (MR_Path.interval == 2) then
		self.intervalMenu:SetChecked(MR_Path.INTERVAL_2, true)
	end
	self.intervalPopup:Redraw()
end

function MR_PathFloatingPanelDialog:HandleMessage(msg)
	local helper = MOHO.ScriptInterfaceHelper:new_local()
	local moho = helper:MohoObject()
	local document = moho.document
	
	local isTarget = false
	if MR_Path.trackingList.targetID[1] then
		isTarget = true
	end
	if msg == self.ADD_TO_TRACKING then
		if document then
			MR_Path:AddToTracking(moho)
			self.createPathButton:Enable(isTarget)
		end
	elseif msg == self.REMOVE_FROM_TRACKING then
		if document then
			MR_Path:RemoveFromTracking(moho)
			self.createPathButton:Enable(isTarget)
		end
	elseif msg == self.CLEAR_TRECKING_LIST then
		if document then
			MR_Path.trackingList = {}
			MR_Path.trackingList.target = {}
			MR_Path.trackingList.targetType = {}
			MR_Path.trackingList.targetID = {}
			MR_Path.trackingList.targetName = {}
			MR_Path.trackingList.layerName = {}
			MR_Path.trackingList.layerUUID = {}
			MR_Path.trackingList.layer = {}
			MR_Path.trackingList.boneTip = {}
			MR_Path.trackingList.color = {}
			MR_Path.trackingList.description = {}
			MR_Path.trackingList.pos = {}
			self.createPathButton:Enable(isTarget)
		end
	elseif msg == self.CREATE_PATH then
		if document then
			MR_Path:CreatePath(moho)
		end
	elseif msg == self.CLEAR_PATH then
		if document then
			MR_Path:ClearPath(moho)
		end
	elseif msg == self.DELETE_PATH_LAYER then
		if document then
			MR_Path:CleanUpInvalidTargets(moho)
			MR_Path:DeletePathLayer(moho)
		end
	elseif msg == self.SHOW_HIDE_PATH then
		if document then
			local pathLayer, targetLayer = MR_Path:CheckExistPathLayer(moho)
			if pathLayer then
				pathLayer:SetVisible(self.showHidePathCheck:Value())
				moho:UpdateUI()
			end
		end
	elseif msg == self.PATH_OPACITY then
		if document then
			MR_Path.pathOpacity = LM.Clamp(self.pathOpacityInput:Value(),0 , 100)
			self.pathOpacityInput:SetValue(MR_Path.pathOpacity)
			local pathLayer, targetLayer = MR_Path:CheckExistPathLayer(moho)
			if pathLayer then
				moho.document:SetDirty()
				moho.document:PrepUndo(pathLayer, true)
				pathLayer.fAlpha:SetValue(0, MR_Path.pathOpacity/100)
				moho:UpdateUI()
				moho.view:DrawMe()
				pathLayer:UpdateCurFrame()
			end
		end
	elseif msg == self.WIDTH then
		MR_Path.width = LM.Clamp(self.widthInput:Value(), 0.25, 256)
		self.widthInput:SetValue(MR_Path.width)
	elseif msg == self.COLOR then
        local colorSwatchValue = self.pathColorSwatch:Value()
		MR_Path.pathColorR = colorSwatchValue.r
		MR_Path.pathColorG = colorSwatchValue.g
		MR_Path.pathColorB = colorSwatchValue.b
		MR_Path.pathColorA = colorSwatchValue.a	
	elseif msg == self.DRAW_CENTER_LINE then
		MR_Path.centerLine = self.centerLineCheck:Value()
	elseif msg == self.DRAW_TIMING_MARK then
		MR_Path.drawTimingMark = self.drawTimingMarkCheck:Value()
	elseif msg == self.TIMING_MARKS_SIZE then
		MR_Path.timingMarksSize = LM.Clamp(self.timingMarksSizeInput:Value(), 0.1, 6)
		self.timingMarksSizeInput:SetValue(MR_Path.timingMarksSize)
	elseif msg == self.TRACK_BONE_TIP then
		MR_Path.trackBoneTip = self.trackBoneTipCheck:Value()
	elseif msg == self.ADJACENT_FRAMES_RANGE then
		MR_Path.adjacentFramesRange = true
		self.adjacentFramesRangeCheck:SetValue(true)
		MR_Path.playbackRange = false
		self.playbackRangeCheck:SetValue(false)
		MR_Path.fromToRange = false
		self.fromToRangeCheck:SetValue(false)
		self.adjacentFramesInput:Enable(true)
		self.fromInput:Enable(false)
		self.toInput:Enable(false)
	elseif msg == self.ADJACENT_FRAMES then
		MR_Path.adjacentFrames = LM.Clamp(self.adjacentFramesInput:Value(), 1, 200)
		self.adjacentFramesInput:SetValue(MR_Path.adjacentFrames)
	elseif msg == self.PLAYBACK_RANGE then
		MR_Path.playbackRange = true
		self.playbackRangeCheck:SetValue(true)
		MR_Path.adjacentFramesRange = false
		self.adjacentFramesRangeCheck:SetValue(false)
		MR_Path.fromToRange = false
		self.fromToRangeCheck:SetValue(false)
		self.adjacentFramesInput:Enable(false)
		self.fromInput:Enable(false)
		self.toInput:Enable(false)
	elseif msg == self.FROM_TO_RANGE then
		MR_Path.fromToRange = true
		self.fromToRangeCheck:SetValue(true)
		MR_Path.adjacentFramesRange = false
		self.adjacentFramesRangeCheck:SetValue(false)
		MR_Path.playbackRange = false
		self.playbackRangeCheck:SetValue(false)
		self.adjacentFramesInput:Enable(false)
		self.fromInput:Enable(true)
		self.toInput:Enable(true)
	elseif msg == self.FROM then	
		MR_Path.from = LM.Clamp(self.fromInput:Value(), 1, self.toInput:Value() - 1)
		self.fromInput:SetValue(MR_Path.from)
	elseif msg == self.TO then
		MR_Path.to = LM.Clamp(self.toInput:Value(), self.fromInput:Value() + 1, 2000)
		self.toInput:SetValue(MR_Path.to)
	elseif (msg >= self.INTERVAL_1 and msg <= self.INTERVAL_2) then
		local int = 1
		if (msg == self.INTERVAL_1) then
			int = 1
		elseif (msg == self.INTERVAL_2) then
			int = 2
		end	
		MR_Path.interval = int
	elseif msg == self.PATH_TARGET_MANAGER then
		if document then
			MR_Path:BuildDialog(moho)
			self.createPathButton:Enable(isTarget)
		end
	end
	
	if document then
		if moho:CurrentTool() == 'MR_Path' then
			MR_Path:UpdateWidgets(moho)
		end
		self:UpdateWidgets(moho)
	end
	helper:delete()
end	

function MR_PathFloatingPanelDialog:OnOK(moho)
	MR_Path.floatingPanelDlog = nil
end

local MR_PathManagerDialog = {}

MR_PathManagerDialog.dynamicNumberText = {}
MR_PathManagerDialog.resetButton = {}
MR_PathManagerDialog.dynamicSymbolText = {}
MR_PathManagerDialog.targetType = {}
MR_PathManagerDialog.targetID = {}
MR_PathManagerDialog.layer = {}
MR_PathManagerDialog.boneTip = {}
MR_PathManagerDialog.color = {}
MR_PathManagerDialog.removeCheck = {}
MR_PathManagerDialog.editedStatus = {}
MR_PathManagerDialog.edited = '*'
MR_PathManagerDialog.notEdited = ' '

MR_PathManagerDialog.CHANGE = MOHO.MSG_BASE
MR_PathManagerDialog.RANDOMIZE_COLORS = MOHO.MSG_BASE + 1
MR_PathManagerDialog.RESET_SETTINGS = MOHO.MSG_BASE + 2
MR_PathManagerDialog.COPY_SETTINGS = MOHO.MSG_BASE + MR_Path.maxTargets + 2

function MR_PathManagerDialog:new(moho)
    local d = LM.GUI.SimpleDialog(MR_Path:Localize('UILabel'), MR_PathManagerDialog)
    local l = d:GetLayout()
	
	d.dynamicHeaderText = LM.GUI.DynamicText(MR_Path:Localize('Path Target Manager'), 0)
	l:AddChild(d.dynamicHeaderText, LM.GUI.ALIGN_CENTER, 0)
	
	l:AddChild(LM.GUI.Divider(false), LM.GUI.ALIGN_FILL)
	local listLength = #MR_Path.trackingList.targetID
	if not MR_Path.trackingList.targetID[1] then
		d.dynamicText = LM.GUI.DynamicText(MR_Path:Localize('Path target list is empty'), 0)
		l:AddChild(d.dynamicText, LM.GUI.ALIGN_LEFT, 0)
	else
		l:PushH()
			for i = 1, listLength do
				d.editedStatus[i] = d.notEdited
				
				if i == 1 then
					l:PushV()
				elseif i == 16 then
					l:Pop()
					l:AddChild(LM.GUI.Divider(true), LM.GUI.ALIGN_FILL)
					l:PushV()
				elseif i == 31 or i == 46 then
					l:Pop()
					l:AddChild(LM.GUI.Divider(true), LM.GUI.ALIGN_FILL)
					l:PushV()
				elseif i == 61 then
					l:Pop()	
				end
				if i <= MR_Path.maxTargets then
					l:PushH()
						local numberText
						if i < 10 then
							numberText = '  '..i
						else
							numberText = ''..i
						end	
						local dynamicTxt = LM.GUI.DynamicText(numberText, 0)
						d.dynamicNumberText[i] = dynamicTxt
						l:AddChild(d.dynamicNumberText[i], LM.GUI.ALIGN_LEFT, 0)
						
						local symbolText = d.editedStatus[i]
						
						d.dynamicSymbolText[i] = LM.GUI.DynamicText(symbolText, 0)
						d.dynamicSymbolText[i]:SetToolTip(MR_Path:Localize('Settings not changed'))
						l:AddChild(d.dynamicSymbolText[i], LM.GUI.ALIGN_LEFT, 0)
						
						d.resetButton[i] = LM.GUI.Button('R', self.RESET_SETTINGS + i)
						d.resetButton[i]:SetToolTip(MR_Path:Localize('Reset settings'))
						d.resetButton[i]:SetAlternateMessage(self.COPY_SETTINGS + i)
						l:AddChild(d.resetButton[i], LM.GUI.ALIGN_LEFT, 0)
						
						local typeText = MR_Path.trackingList.targetType[i]
						local layerNameText = ' Layer: \"'.. MR_Path.trackingList.layer[i]:Name()..'\"'
						local targetID = MR_Path.trackingList.targetID[i]
						local targetIDText = targetID
						
						targetIDText = ' ID: '.. targetIDText
						
						d.targetType[i] = LM.GUI.DynamicText(typeText..targetIDText, 0)
						d.targetType[i]:SetToolTip(MR_Path.trackingList.description[i]..layerNameText)
						l:AddChild(d.targetType[i], LM.GUI.ALIGN_LEFT, 0)
						
						if targetID < 10 then
							l:AddPadding(14)
						elseif targetID < 100 then	
							l:AddPadding(8)
						elseif targetID < 1000 then	
							l:AddPadding(2)
						else
							l:AddPadding(-4)
						end
						
						d.color[i] = LM.GUI.ShortColorSwatch(true, self.CHANGE)
						d.color[i]:SetToolTip(MR_Path:Localize('Path Color Tooltip'))
						l:AddChild(d.color[i], LM.GUI.ALIGN_LEFT)
						
						if MR_Path.trackingList.targetType[i] == "Point" then
							d.boneTip[i] = LM.GUI.CheckBox(MR_Path:Localize(''), d.CHANGE)
							d.boneTip[i]:SetToolTip(MR_Path:Localize('Track bone tip'))
							l:AddChild(d.boneTip[i], LM.GUI.ALIGN_LEFT, 0)
							d.boneTip[i]:Enable(false)
						elseif MR_Path.trackingList.targetType[i] == "Bone" then
							d.boneTip[i] = LM.GUI.CheckBox(MR_Path:Localize(''), d.CHANGE)
							d.boneTip[i]:SetToolTip(MR_Path:Localize('Track bone tip'))
							l:AddChild(d.boneTip[i], LM.GUI.ALIGN_LEFT, 0)
						end
						
						l:AddChild(LM.GUI.Divider(true), LM.GUI.ALIGN_FILL)
						
						l:AddPadding(1)
						
						d.removeCheck[i] = LM.GUI.CheckBox(MR_Path:Localize(''), d.CHANGE)
						d.removeCheck[i]:SetToolTip(MR_Path:Localize('Remove from tracking'))
						l:AddChild(d.removeCheck[i], LM.GUI.ALIGN_LEFT, 0)
						
					l:Pop()
					if i == listLength and i ~= 16 and i ~= 31 and i ~= 46 and i ~= 61 then
						l:Pop()
					end
				end
			end	
		l:Pop()
		d.randomizeColorsButton = LM.GUI.Button(MR_Path:Localize('Randomize colors'), self.RANDOMIZE_COLORS)
		l:AddChild(d.randomizeColorsButton, LM.GUI.ALIGN_RIGHT, 0)
	end	
	return d
end	

function MR_PathManagerDialog:UpdateWidgets(moho)
	for i = 1, #MR_Path.trackingList.targetID do
		self.color[i]:SetValue(MR_Path.trackingList.color[i])
		self.boneTip[i]:SetValue(MR_Path.trackingList.boneTip[i])
	end
end

function MR_PathManagerDialog:HandleMessage(msg)
	local targetListLength = #MR_Path.trackingList.targetID
    if msg == self.CHANGE then
		self:CheckTargetStatus(moho)
    elseif msg == self.RANDOMIZE_COLORS then
		for i = 1, targetListLength do
			local pathColor = LM.rgb_color:new_local()
			pathColor.r = math.random(0, 255)
			pathColor.g = math.random(0, 255)
			pathColor.b = math.random(0, 255)
			pathColor.a = 255
			self.color[i]:SetValue(pathColor)
		end
		self:CheckTargetStatus(moho)
    elseif msg >= self.RESET_SETTINGS and msg <= self.RESET_SETTINGS + targetListLength then
		local index = msg - self.RESET_SETTINGS
		self.color[index]:SetValue(MR_Path.trackingList.color[index])
		self.boneTip[index]:SetValue(MR_Path.trackingList.boneTip[index])
		self.removeCheck[index]:SetValue(false)
		self:CheckTargetStatus(moho)
    elseif msg >= self.COPY_SETTINGS and msg <= self.COPY_SETTINGS + targetListLength then
		local index = msg - self.COPY_SETTINGS
		for i = 1, targetListLength do
			self.color[i]:SetValue(self.color[index]:Value())
			if MR_Path.trackingList.targetType[i] == 'Bone' then
				self.boneTip[i]:SetValue(self.boneTip[index]:Value())
			end	
			self.removeCheck[i]:SetValue(self.removeCheck[index]:Value())
		end
		self:CheckTargetStatus(moho)
   end
end

function MR_PathManagerDialog:CheckTargetStatus(moho)
	for i = 1, #MR_Path.trackingList.targetID do
		local isSettingsChanged = false
		local color = self.color[i]:Value()
		if color.r ~= MR_Path.trackingList.color[i].r
		or color.g ~= MR_Path.trackingList.color[i].g
		or color.b ~= MR_Path.trackingList.color[i].b
		or color.a ~= MR_Path.trackingList.color[i].a then
			isSettingsChanged = true
		end	
		if self.boneTip[i]:Value() ~= MR_Path.trackingList.boneTip[i] then	
			isSettingsChanged = true
		end
		if isSettingsChanged then
			self.editedStatus[i] = self.edited
			self.dynamicSymbolText[i]:SetValue(self.editedStatus[i])
			self.dynamicSymbolText[i]:SetToolTip(MR_Path:Localize('Settings changed'))
		else
			self.editedStatus[i] = self.notEdited
			self.dynamicSymbolText[i]:SetValue(self.editedStatus[i])
			self.dynamicSymbolText[i]:SetToolTip(MR_Path:Localize('Settings not changed'))
		end
	end
end

function MR_PathManagerDialog:OnOK(moho)
	for i = 1, #MR_Path.trackingList.targetID do
		local colorSwatchValue = self.color[i]:Value()
		self.pathColorR = colorSwatchValue.r
		self.pathColorG = colorSwatchValue.g
		self.pathColorB = colorSwatchValue.b
		self.pathColorA = colorSwatchValue.a
		MR_Path.trackingList.color[i] = colorSwatchValue
		MR_Path.trackingList.boneTip[i] = self.boneTip[i]:Value()
		MR_Path.removeList[i] = self.removeCheck[i]:Value()
	end	
end

-- **************************************************
-- Tool Panel Layout
-- **************************************************

MR_Path.ADD_TO_TRACKING = MOHO.MSG_BASE
MR_Path.REMOVE_FROM_TRACKING = MOHO.MSG_BASE + 1
MR_Path.CLEAR_TRECKING_LIST = MOHO.MSG_BASE + 2
MR_Path.CREATE_PATH = MOHO.MSG_BASE + 3
MR_Path.CLEAR_PATH = MOHO.MSG_BASE + 4
MR_Path.DELETE_PATH_LAYER = MOHO.MSG_BASE + 5
MR_Path.SHOW_HIDE_PATH = MOHO.MSG_BASE + 6
MR_Path.PATH_OPACITY = MOHO.MSG_BASE + 7
MR_Path.WIDTH = MOHO.MSG_BASE + 8
MR_Path.COLOR = MOHO.MSG_BASE + 9
MR_Path.DRAW_CENTER_LINE = MOHO.MSG_BASE + 10
MR_Path.DRAW_TIMING_MARK = MOHO.MSG_BASE + 11
MR_Path.TIMING_MARKS_SIZE = MOHO.MSG_BASE + 12
MR_Path.TRACK_BONE_TIP = MOHO.MSG_BASE + 13
MR_Path.ADJACENT_FRAMES_RANGE = MOHO.MSG_BASE + 14
MR_Path.ADJACENT_FRAMES = MOHO.MSG_BASE + 15
MR_Path.PLAYBACK_RANGE = MOHO.MSG_BASE + 16
MR_Path.FROM_TO_RANGE = MOHO.MSG_BASE + 17
MR_Path.FROM = MOHO.MSG_BASE + 18
MR_Path.TO = MOHO.MSG_BASE + 19
MR_Path.INTERVAL_1 = MOHO.MSG_BASE + 20
MR_Path.INTERVAL_2 = MOHO.MSG_BASE + 21
MR_Path.PATH_TARGET_MANAGER = MOHO.MSG_BASE + 22
MR_Path.OPEN_IN_PANEL = MOHO.MSG_BASE + 23

function MR_Path:DoLayout(moho, layout)
	self.addToTrackingButton = LM.GUI.ImageButton('ScriptResources/mr_path/mr_add_to_tracking', self:Localize('Add to tracking'), false, self.ADD_TO_TRACKING, false)
	layout:AddChild(self.addToTrackingButton, LM.GUI.ALIGN_LEFT, 0)

	self.removeFromTrackingButton = LM.GUI.ImageButton('ScriptResources/mr_path/mr_remove_from_tracking', self:Localize('Remove from tracking'), false, self.REMOVE_FROM_TRACKING, false)
	layout:AddChild(self.removeFromTrackingButton, LM.GUI.ALIGN_LEFT, 0)

	self.clearTrackingListButton = LM.GUI.ImageButton('ScriptResources/mr_path/mr_clear_tracking_list', self:Localize('Clear tracking list'), false, self.CLEAR_TRECKING_LIST, false)
	layout:AddChild(self.clearTrackingListButton, LM.GUI.ALIGN_LEFT, 0)
	
	layout:AddChild(LM.GUI.Divider(true), LM.GUI.ALIGN_FILL)

	self.createPathButton = LM.GUI.ImageButton('ScriptResources/mr_path/mr_create_path', self:Localize('Create path'), false, self.CREATE_PATH, false)
	layout:AddChild(self.createPathButton, LM.GUI.ALIGN_LEFT, 0)

	self.clearPathButton = LM.GUI.ImageButton('ScriptResources/mr_path/mr_clear_path', self:Localize('Clear path'), false, self.CLEAR_PATH, false)
	layout:AddChild(self.clearPathButton, LM.GUI.ALIGN_LEFT, 0)

	self.deletePathLayerButton = LM.GUI.ImageButton('ScriptResources/mr_path/mr_delete_path_layer', self:Localize('Delete path layer'), false, self.DELETE_PATH_LAYER, false)
	layout:AddChild(self.deletePathLayerButton, LM.GUI.ALIGN_LEFT, 0)
	
	layout:AddChild(LM.GUI.Divider(true), LM.GUI.ALIGN_FILL)

	self.showHidePathCheck = LM.GUI.ImageButton('ScriptResources/mr_path/mr_visibility', self:Localize('Show hide'), true, self.SHOW_HIDE_PATH, false)
	layout:AddChild(self.showHidePathCheck, LM.GUI.ALIGN_LEFT, 0)

	self.pathOpacityInput = LM.GUI.TextControl(0, '100', self.PATH_OPACITY, LM.GUI.FIELD_INT, self:Localize('Path Opacity:'))
	self.pathOpacityInput:SetToolTip(self:Localize('Opacity for all Paths'))
	layout:AddChild(self.pathOpacityInput, LM.GUI.ALIGN_LEFT, 0)

	self.widthInput = LM.GUI.TextControl(0, '100', self.WIDTH, LM.GUI.FIELD_UFLOAT, self:Localize('Width:'))
	self.widthInput:SetWheelInc(1.0)
	self.widthInput:SetWheelInteger(true)
	self.widthInput:SetToolTip(self:Localize('Width for all Paths'))
	layout:AddChild(self.widthInput, LM.GUI.ALIGN_LEFT, 0)
	
	self.colorText = LM.GUI.DynamicText(self:Localize('Color'), 0)
    layout:AddChild(self.colorText, LM.GUI.ALIGN_LEFT, 0)
	
	self.pathColorSwatch = LM.GUI.ShortColorSwatch(true, self.COLOR)
	layout:AddChild(self.pathColorSwatch, LM.GUI.ALIGN_LEFT)
	self.pathColorSwatch:SetToolTip(MR_Path:Localize('Path Color Tooltip'))
	
	layout:AddChild(LM.GUI.Divider(true), LM.GUI.ALIGN_FILL)
	
	self.centerLineCheck = LM.GUI.ImageButton('ScriptResources/mr_path/mr_center_line', self:Localize('Draw center line'), true, self.DRAW_CENTER_LINE, false)
	layout:AddChild(self.centerLineCheck, LM.GUI.ALIGN_LEFT, 0)
	
	self.drawTimingMarkCheck = LM.GUI.ImageButton('ScriptResources/mr_path/mr_draw_timing_marks', self:Localize('Draw timing marks'), true, self.DRAW_TIMING_MARK, false)
	layout:AddChild(self.drawTimingMarkCheck, LM.GUI.ALIGN_LEFT, 0)
	
	self.timingMarksSizeInput = LM.GUI.TextControl(0, '100', self.TIMING_MARKS_SIZE, LM.GUI.FIELD_FLOAT, self:Localize('Size:'))
	self.timingMarksSizeInput:SetWheelInc(0.01)
	self.timingMarksSizeInput:SetToolTip(self:Localize('Timing marks size'))
	layout:AddChild(self.timingMarksSizeInput, LM.GUI.ALIGN_LEFT, 0)
	
	layout:AddChild(LM.GUI.Divider(true), LM.GUI.ALIGN_FILL)
	
	self.trackBoneTipCheck = LM.GUI.ImageButton('ScriptResources/mr_path/mr_track_bone_tip', self:Localize('Track bone tip'), true, self.TRACK_BONE_TIP, false)
	layout:AddChild(self.trackBoneTipCheck, LM.GUI.ALIGN_LEFT, 0)
	
	layout:AddChild(LM.GUI.Divider(true), LM.GUI.ALIGN_FILL)
	
	self.adjacentFramesRangeCheck = LM.GUI.ImageButton('ScriptResources/mr_path/mr_adjacent_frames', self:Localize('Adjacent frames range'), true, self.ADJACENT_FRAMES_RANGE, false)
	layout:AddChild(self.adjacentFramesRangeCheck, LM.GUI.ALIGN_LEFT, 0)
	
	self.adjacentFramesInput = LM.GUI.TextControl(0, '100', self.ADJACENT_FRAMES, LM.GUI.FIELD_INT, '')
	self.adjacentFramesInput:SetToolTip(self:Localize('Adjacent frames'))
	layout:AddChild(self.adjacentFramesInput, LM.GUI.ALIGN_LEFT, 0)
	
	layout:AddChild(LM.GUI.Divider(true), LM.GUI.ALIGN_FILL)
	
	self.playbackRangeCheck = LM.GUI.ImageButton('ScriptResources/mr_path/mr_playback_range', self:Localize('Playback range'), true, self.PLAYBACK_RANGE, false)
	layout:AddChild(self.playbackRangeCheck, LM.GUI.ALIGN_LEFT, 0)
	
	layout:AddChild(LM.GUI.Divider(true), LM.GUI.ALIGN_FILL)
	
	self.fromToRangeCheck = LM.GUI.ImageButton('ScriptResources/mr_path/mr_from_to', self:Localize('From to range'), true, self.FROM_TO_RANGE, false)
	layout:AddChild(self.fromToRangeCheck, LM.GUI.ALIGN_LEFT, 0)
	
	self.fromInput = LM.GUI.TextControl(0, '100', self.FROM, LM.GUI.FIELD_INT, '')
	self.fromInput:SetToolTip(self:Localize('From'))
	layout:AddChild(self.fromInput, LM.GUI.ALIGN_LEFT, 0)
	
	self.toInput = LM.GUI.TextControl(0, '100', self.TO, LM.GUI.FIELD_INT, '')
	self.toInput:SetToolTip(self:Localize('To'))
	layout:AddChild(self.toInput, LM.GUI.ALIGN_LEFT, 0)
	
	layout:AddChild(LM.GUI.Divider(true), LM.GUI.ALIGN_FILL)
	
	layout:AddChild(LM.GUI.StaticText(self:Localize("IntervalText")))

	self.intervalMenu = LM.GUI.Menu(MOHO.Localize("Interval=Interval"))
	self.intervalMenu:AddItem(MOHO.Localize("1=1"), 0, self.INTERVAL_1)
	self.intervalMenu:AddItemAlphabetically(MOHO.Localize("2=2"), 0, self.INTERVAL_2)

	self.intervalPopup = LM.GUI.PopupMenu(50, true)
	self.intervalPopup:SetMenu(self.intervalMenu)
	layout:AddChild(self.intervalPopup)
	
	layout:AddChild(LM.GUI.Divider(true), LM.GUI.ALIGN_FILL)
	
	self.PathTargetManagerButton = LM.GUI.Button(self:Localize('Path Target Manager'), self.PATH_TARGET_MANAGER)
	layout:AddChild(self.PathTargetManagerButton, LM.GUI.ALIGN_LEFT, 0)
	
	layout:AddChild(LM.GUI.Divider(true), LM.GUI.ALIGN_FILL)
	
	local v1, v2, v3 = self:GetMohoVersion(moho)
	if not v3 then
		v3 = 0
	end
	
	local modelessDialogSupport = false
	if v1 >= 14 and v2 >= 1 then
		modelessDialogSupport = true
	end
	
	if modelessDialogSupport then
		self.openInPanelButton = LM.GUI.Button(self:Localize('Open in panel'), self.OPEN_IN_PANEL)
		layout:AddChild(self.openInPanelButton, LM.GUI.ALIGN_LEFT, 0)
		
		layout:AddChild(LM.GUI.Divider(true), LM.GUI.ALIGN_FILL)
	end
	
	layout:AddChild(LM.GUI.StaticText('v'..MR_Path:Version()))
end

function MR_Path:UpdateWidgets(moho)
	if self.isTracking then
		return
	end
	
	local pathLayer, targetLayer = self:CheckExistPathLayer(moho)
	local isPathLayer = false
	if pathLayer then
		isPathLayer = true
	end
	
	local isTarget = false
	if self.trackingList.targetID[1] then
		isTarget = true
	end
	
	self.createPathButton:Enable(isTarget)
	self.clearPathButton:Enable(isPathLayer)
	self.deletePathLayerButton:Enable(isPathLayer)
	self.showHidePathCheck:Enable(isPathLayer)
	if pathLayer then
		self.showHidePathCheck:SetValue(pathLayer:IsVisible())
		pathLayer.fAlpha:GetValue(0)
	else
		self.pathOpacityInput:SetValue(self.pathOpacity)
		self.showHidePathCheck:SetValue(true)
	end
	self.widthInput:SetValue(self.width)
	local pathColor = LM.rgb_color:new_local()
	pathColor.r = self.pathColorR
	pathColor.g = self.pathColorG
	pathColor.b = self.pathColorB
	pathColor.a = self.pathColorA
	self.pathColorSwatch:SetValue(pathColor)
	self.centerLineCheck:SetValue(self.centerLine)
	self.drawTimingMarkCheck:SetValue(self.drawTimingMark)
	self.timingMarksSizeInput:SetValue(self.timingMarksSize)
	self.trackBoneTipCheck:SetValue(self.trackBoneTip)
	self.adjacentFramesRangeCheck:SetValue(self.adjacentFramesRange)
	self.adjacentFramesInput:SetValue(self.adjacentFrames)
	self.adjacentFramesInput:Enable(self.adjacentFramesRange)
	self.playbackRangeCheck:SetValue(self.playbackRange)
	self.fromToRangeCheck:SetValue(self.fromToRange)
	self.fromInput:SetValue(self.from)
	self.fromInput:Enable(self.fromToRange)
	self.toInput:SetValue(self.to)
	self.toInput:Enable(self.fromToRange)
	
	self.intervalMenu:SetChecked(self.INTERVAL_1, false)
	self.intervalMenu:SetChecked(self.INTERVAL_2, false)
	if (self.interval == 1) then
		self.intervalMenu:SetChecked(self.INTERVAL_1, true)
	elseif (self.interval == 2) then
		self.intervalMenu:SetChecked(self.INTERVAL_2, true)
	end
	self.intervalPopup:Redraw()
end

function MR_Path:HandleMessage(moho, view, msg)
	local isTarget = false
	if self.trackingList.targetID[1] then
		isTarget = true
	end
	if msg == self.ADD_TO_TRACKING then
		self:AddToTracking(moho)
		self.createPathButton:Enable(isTarget)
	elseif msg == self.REMOVE_FROM_TRACKING then
		self:RemoveFromTracking(moho)
		self.createPathButton:Enable(isTarget)
	elseif msg == self.CLEAR_TRECKING_LIST then
		self.trackingList = {}
		self.trackingList.target = {}
		self.trackingList.targetType = {}
		self.trackingList.targetID = {}
		self.trackingList.targetName = {}
		self.trackingList.layerName = {}
		self.trackingList.layerUUID = {}
		self.trackingList.layer = {}
		self.trackingList.boneTip = {}
		self.trackingList.color = {}
		self.trackingList.description = {}
		self.trackingList.pos = {}
		self.createPathButton:Enable(isTarget)
	elseif msg == self.CREATE_PATH then
		self:CreatePath(moho)
	elseif msg == self.CLEAR_PATH then
		self:ClearPath(moho)
	elseif msg == self.DELETE_PATH_LAYER then
		self:CleanUpInvalidTargets(moho)
		self:DeletePathLayer(moho)
	elseif msg == self.SHOW_HIDE_PATH then
		local pathLayer, targetLayer = self:CheckExistPathLayer(moho)
		if pathLayer then
			pathLayer:SetVisible(self.showHidePathCheck:Value())
			moho:UpdateUI()
		end	
	elseif msg == self.PATH_OPACITY then
		self.pathOpacity = LM.Clamp(self.pathOpacityInput:Value(),0 , 100)
		self.pathOpacityInput:SetValue(self.pathOpacity)
		local pathLayer, targetLayer = self:CheckExistPathLayer(moho)
		if pathLayer then
			moho.document:SetDirty()
			moho.document:PrepUndo(pathLayer, true)
			pathLayer.fAlpha:SetValue(0, self.pathOpacity/100)
			moho:UpdateUI()
			moho.view:DrawMe()
			pathLayer:UpdateCurFrame()
		end
	elseif msg == self.WIDTH then
		self.width = LM.Clamp(self.widthInput:Value(), 0.25, 256)
		self.widthInput:SetValue(self.width)
	elseif msg == self.COLOR then
        local colorSwatchValue = self.pathColorSwatch:Value()
		self.pathColorR = colorSwatchValue.r
		self.pathColorG = colorSwatchValue.g
		self.pathColorB = colorSwatchValue.b
		self.pathColorA = colorSwatchValue.a	
	elseif msg == self.DRAW_CENTER_LINE then
		self.centerLine = self.centerLineCheck:Value()
	elseif msg == self.DRAW_TIMING_MARK then
		self.drawTimingMark = self.drawTimingMarkCheck:Value()
	elseif msg == self.TIMING_MARKS_SIZE then
		self.timingMarksSize = LM.Clamp(self.timingMarksSizeInput:Value(), 0.1, 6)
		self.timingMarksSizeInput:SetValue(self.timingMarksSize)
	elseif msg == self.TRACK_BONE_TIP then
		self.trackBoneTip = self.trackBoneTipCheck:Value()
	elseif msg == self.ADJACENT_FRAMES_RANGE then
		self.adjacentFramesRange = true
		self.adjacentFramesRangeCheck:SetValue(true)
		self.playbackRange = false
		self.playbackRangeCheck:SetValue(false)
		self.fromToRange = false
		self.fromToRangeCheck:SetValue(false)
		self.adjacentFramesInput:Enable(true)
		self.fromInput:Enable(false)
		self.toInput:Enable(false)
	elseif msg == self.ADJACENT_FRAMES then
		self.adjacentFrames = LM.Clamp(self.adjacentFramesInput:Value(), 1, 200)
		self.adjacentFramesInput:SetValue(self.adjacentFrames)
	elseif msg == self.PLAYBACK_RANGE then
		self.playbackRange = true
		self.playbackRangeCheck:SetValue(true)
		self.adjacentFramesRange = false
		self.adjacentFramesRangeCheck:SetValue(false)
		self.fromToRange = false
		self.fromToRangeCheck:SetValue(false)
		self.adjacentFramesInput:Enable(false)
		self.fromInput:Enable(false)
		self.toInput:Enable(false)
	elseif msg == self.FROM_TO_RANGE then
		self.fromToRange = true
		self.fromToRangeCheck:SetValue(true)
		self.adjacentFramesRange = false
		self.adjacentFramesRangeCheck:SetValue(false)
		self.playbackRange = false
		self.playbackRangeCheck:SetValue(false)
		self.adjacentFramesInput:Enable(false)
		self.fromInput:Enable(true)
		self.toInput:Enable(true)
	elseif msg == self.FROM then	
		self.from = LM.Clamp(self.fromInput:Value(), 1, self.toInput:Value() - 1)
		self.fromInput:SetValue(self.from)
	elseif msg == self.TO then
		self.to = LM.Clamp(self.toInput:Value(), self.fromInput:Value() + 1, 2000)
		self.toInput:SetValue(self.to)
	elseif (msg >= self.INTERVAL_1 and msg <= self.INTERVAL_2) then
		local int = 1
		if (msg == self.INTERVAL_1) then
			int = 1
		elseif (msg == self.INTERVAL_2) then
			int = 2
		end	
		self.interval = int
	elseif msg == self.PATH_TARGET_MANAGER then
		self:BuildDialog(moho)
		self.createPathButton:Enable(isTarget)
	elseif msg == self.OPEN_IN_PANEL then
		self:BuildFloatingPanelDialog(moho)
	end
	self:UpdateWidgets(moho)
	if self.floatingPanelDlog then
		self.floatingPanelDlog:UpdateWidgets(moho)
	end
end

function MR_Path:AddToTracking(moho)
	local skel = moho:Skeleton()
	local mesh = moho:Mesh()
	if not skel and not mesh then
		return
	end
	
	self:CleanUpInvalidTargets(moho)
	
	local pathColor = LM.rgb_color:new_local()
	pathColor.r = self.pathColorR
	pathColor.g = self.pathColorG
	pathColor.b = self.pathColorB
	pathColor.a = self.pathColorA
	
	local outOfLimit = 0
	
	if mesh and moho:CountSelectedPoints() > 0 then
		for i = 0, mesh:CountPoints()-1 do
			local point = mesh:Point(i)
			if point.fSelected  then
				local existInList = self:ValueExists(self.trackingList.target, point)
				if existInList then
					self.trackingList.target[existInList] = point
					self.trackingList.targetType[existInList] = 'Point'
					self.trackingList.targetID[existInList] = i
					self.trackingList.targetName[existInList] = i
					self.trackingList.layerName[existInList] = moho.layer:Name()
					self.trackingList.layerUUID[existInList] = moho.layer:UUID()
					self.trackingList.layer[existInList] = moho.layer
					self.trackingList.boneTip[existInList] = false
					self.trackingList.color[existInList] = pathColor
					local skel = moho.layer:ControllingSkeleton()
					if skel and point.fParent >= 0 then
						self.trackingList.description[existInList] = 'Parent bone: \"' .. skel:Bone(point.fParent):Name()..'\" ID: '..point.fParent
					else
						self.trackingList.description[existInList] = 'No parent bone.'
					end	
				elseif #self.trackingList.targetID < self.maxTargets then
					table.insert(self.trackingList.target, point)
					table.insert(self.trackingList.targetType, 'Point')
					table.insert(self.trackingList.targetID, i)
					table.insert(self.trackingList.targetName, i)
					table.insert(self.trackingList.layerName, moho.layer:Name())
					table.insert(self.trackingList.layerUUID, moho.layer:UUID())
					table.insert(self.trackingList.layer, moho.layer)
					table.insert(self.trackingList.boneTip, false)
					table.insert(self.trackingList.color, pathColor)
					local skel = moho.layer:ControllingSkeleton()
					if skel and point.fParent >= 0 then
						table.insert(self.trackingList.description, 'Parent bone: \"' .. skel:Bone(point.fParent):Name()..'\" ID: '..point.fParent)
					else
						table.insert(self.trackingList.description, 'No parent bone.')
					end
					local posList = {}
					posList.vec2 = {}
					posList.frame = {}
					table.insert(self.trackingList.pos, posList) 
				else
					outOfLimit = outOfLimit + 1
				end
			end
		end
	end
	
	if skel and moho:CountSelectedBones() > 0 then
		for i = 0, skel:CountBones()-1 do
			local bone = skel:Bone(i)
			if bone.fSelected then
				local existInList = self:ValueExists(self.trackingList.target, bone)
				if existInList then
					self.trackingList.target[existInList] = bone
					self.trackingList.targetType[existInList] = 'Bone'
					self.trackingList.targetID[existInList] = i
					self.trackingList.targetName[existInList] = bone:Name()
					self.trackingList.layerName[existInList] = moho.layer:Name()
					self.trackingList.layerUUID[existInList] = moho.layer:UUID()
					self.trackingList.layer[existInList] = moho.layer
					self.trackingList.boneTip[existInList] = self.trackBoneTip
					self.trackingList.color[existInList] = pathColor
					self.trackingList.description[existInList] = 'Bone: \"' .. bone:Name()..'\"'
				elseif #self.trackingList.targetID < self.maxTargets then
					table.insert(self.trackingList.target, bone)
					table.insert(self.trackingList.targetType, 'Bone')
					table.insert(self.trackingList.targetID, i)
					table.insert(self.trackingList.targetName, bone:Name())
					table.insert(self.trackingList.layerName, moho.layer:Name())
					table.insert(self.trackingList.layerUUID, moho.layer:UUID())
					table.insert(self.trackingList.layer, moho.layer)
					table.insert(self.trackingList.boneTip, self.trackBoneTip)
					table.insert(self.trackingList.color, pathColor)
					table.insert(self.trackingList.description, 'Bone: \"' .. bone:Name()..'\"')
					local posList = {}
					posList.vec2 = {}
					posList.frame = {}
					table.insert(self.trackingList.pos, posList)
				else
					outOfLimit = outOfLimit + 1
				end
			end
		end
	end
	
	if outOfLimit > 0 then
		local infoAlert = LM.GUI.Alert(LM.GUI.ALERT_INFO, self:Localize('Targets limit exceeded alert 1'),
		self:Localize('Targets limit exceeded alert 2')..' '..outOfLimit, "", 'OK')
	end
end

function MR_Path:RemoveFromTracking(moho)
	local skel = moho:Skeleton()
	local mesh = moho:Mesh()
	if not skel and not mesh then
		return
	end
	
	self:CleanUpInvalidTargets(moho)
	
	if mesh and moho:CountSelectedPoints() > 0 then
		for i = 0, mesh:CountPoints()-1 do
			local point = mesh:Point(i)
			if point.fSelected then
				local existInList = self:ValueExists(self.trackingList.target, point)
				if existInList then
					self:RemoveFromTargetList(moho, existInList)
				end
			end
		end
	end
	
	if skel and moho:CountSelectedBones() > 0 then
		for i = 0, skel:CountBones()-1 do
			local bone = skel:Bone(i)
			if bone.fSelected then
				local existInList = self:ValueExists(self.trackingList.target, bone)
				if existInList then
					self:RemoveFromTargetList(moho, existInList)
				end
			end
		end
	end
end

function MR_Path:BuildDialog(moho)
	self:CleanUpInvalidTargets(moho)
	self.dlog = MR_PathManagerDialog:new()
    self.settingsPopup = LM.GUI.PopupDialog(self:Localize('Path Target Manager'), false, 0)
    self.settingsPopup:SetDialog(self.dlog)
	self.removeList = {}
	
	local dlog = MR_PathManagerDialog:new(moho)
	if dlog then
		if (dlog:DoModal() == LM.GUI.MSG_CANCEL) then
			return false
		end
	end
	local removeListLength = #self.removeList
	for i = 1, removeListLength do
		local n = removeListLength + 1 - i
		if self.removeList[n] then
			self:RemoveFromTargetList(moho, n)
		end	
    end
end

function MR_Path:CleanUpInvalidTargets(moho)
	local listLength = #self.trackingList.target
	for i = 1, listLength do
		local n = listLength + 1 - i
		if not moho.document:IsLayerValid(self.trackingList.layerUUID[n]) then
			self:RemoveFromTargetList(moho, n)
		else
			if self.trackingList.targetType[n] == 'Point' then
				local layer = self.trackingList.layer[n]
				if not moho.document:IsLayerValid(layer) then
					layer = self:GetLayerByUUID(moho, self.trackingList.layerUUID[n])
					if layer then
						self.trackingList.layer[n] = layer
					end	
				end
				if layer then
					if self.trackingList.target[n]:CountCurves() == 0 then
						self:RemoveFromTargetList(moho, n)
					end
				else
					self:RemoveFromTargetList(moho, n)
				end	
			elseif self.trackingList.targetType[n] == 'Bone' then
				local boneValid = false
				local layer = self.trackingList.layer[n]
				if not moho.document:IsLayerValid(layer) then
					layer = self:GetLayerByUUID(moho, self.trackingList.layerUUID[n])
					if layer then
						self.trackingList.layer[n] = layer
					end	
				end
				if layer then
					local skel = moho:LayerAsBone(layer):Skeleton()
					if skel then
						for b=0, skel:CountBones()-1 do
							local targetBone = skel:Bone(b)
							if self.trackingList.targetName[n] == targetBone:Name() then
								self.trackingList.target[n] = targetBone
								boneValid = true
								break
							end
						end
					end
				end	
				if not boneValid then
					self:RemoveFromTargetList(moho, n)
				end
			end
		end	
    end
end

function MR_Path:RemoveFromTargetList(moho, n)
	table.remove(self.trackingList.target, n)
	table.remove(self.trackingList.targetType, n)
	table.remove(self.trackingList.targetID, n)
	table.remove(self.trackingList.targetName, n)
	table.remove(self.trackingList.layerName, n)
	table.remove(self.trackingList.layerUUID, n)
	table.remove(self.trackingList.layer, n)
	table.remove(self.trackingList.boneTip, n)
	table.remove(self.trackingList.color, n)
	table.remove(self.trackingList.description, n)
	table.remove(self.trackingList.pos, n)
end

function MR_Path:ValueExists(tbl, value)
    for key, val in pairs(tbl) do
        if val == value then
            return key
        end
    end
    return false
end

function MR_Path:CreatePath(moho)
	self:CleanUpInvalidTargets(moho)
	
	if not self.trackingList.targetID[1] then
		return
	end
	
	local curLayer = moho.layer
	local curFrame = moho.frame
	local curAction = moho.layer:CurrentAction()

	local currentQualityFlags = moho.view:QualityFlags()
	local wireframe = MOHO.hasbit(moho.view:QualityFlags(), MOHO.bit(MOHO.LDQ_WIREFRAME))
	if wireframe then
		moho.view:SetQualityFlags(currentQualityFlags - MOHO.LDQ_WIREFRAME)
	end
	local from = 1
	local to = 20
	
	if self.adjacentFramesRange then
		from = curFrame - self.adjacentFrames
		if from < 1 then from = 1 end
		to = curFrame + self.adjacentFrames
	elseif self.playbackRange then
		from = MOHO.MohoGlobals.PlayStart
		to = MOHO.MohoGlobals.PlayEnd
		if from < 1 then
			from = moho.document:StartFrame()
		end
		if to < 2 then
			to = moho.document:EndFrame()
		end
	elseif self.fromToRange then
		from = self.from
		to = self.to
	end

	if self.interval == 2 then
		local isCurFrameEven = curFrame % 2 == 0
		local isFromFrameEven = from % 2 == 0
		local isToFrameEven = to % 2 == 0
		if isFromFrameEven ~= isCurFrameEven then
			if from == 1 then
				from = from + 1
			elseif from > 1 then
				from = from - 1
			end
		end
		if isToFrameEven ~= isCurFrameEven then
			to = to + 1
		end
	end

	local isSoundtrackMuted = MOHO.MohoGlobals.MuteSoundtrack
	MOHO.MohoGlobals.MuteSoundtrack = true

	local targetFrame = from
	local counter = 1
	
	for i = 1, #self.trackingList.targetID do
		self.trackingList.pos[i].vec2 = {}
		self.trackingList.pos[i].frame = {}
	end
	self.isTracking = true
	repeat
		moho:SetCurFrame(targetFrame)
		for i = 1, #self.trackingList.targetID do
			local pos = LM.Vector2:new_local()
			local layer = self.trackingList.layer[i]
			if self.trackingList.targetType[i] == 'Point' then
				local point = self.trackingList.target[i]
				pos = self:GetGlobalPos(moho, layer, point.fPos)
			elseif self.trackingList.targetType[i] == 'Bone' then
				local bone = self.trackingList.target[i]
				
				if self.trackingList.boneTip[i] then
					if (bone:IsZeroLength()) then
						pos:Set(0, 0)
					else
						pos:Set(bone.fLength, 0)
					end
				else
					pos:Set(0, 0)
				end
				bone.fMovedMatrix:Transform(pos)
				pos = self:GetGlobalPos(moho, layer, pos)
			end
			self.trackingList.pos[i].vec2[counter] = pos
			self.trackingList.pos[i].frame[counter] = targetFrame
		end
		counter = counter + 1
		targetFrame = targetFrame + self.interval
	until targetFrame >= to + 1

	local pathLayer, targetLayer = self:CheckExistPathLayer(moho)
	local pathLayerMesh
	
	if not pathLayer then
		moho.document:SetDirty()
		moho.document:PrepUndo(nil)
		moho:SetSelLayer(targetLayer)
		pathLayer = self:CreateNewLayer(moho)
		pathLayerMesh = moho:LayerAsVector(pathLayer):Mesh()
	else	
		moho.document:PrepUndo(pathLayer, true)
		moho.document:SetDirty()
		pathLayerMesh = moho:LayerAsVector(pathLayer):Mesh()
		if pathLayerMesh then
			pathLayerMesh:Clear()
			pathLayer:SetVisible(true)
			pathLayer.fAlpha:SetValue(0, self.pathOpacity / 100)
		end	
	end
	moho:SetSelLayer(pathLayer)
	moho:SetCurFrame(0)
	
	local pointList = {}
	pointList.point = {}
	pointList.color = {}
	
	for i = 1, #self.trackingList.targetID do
		local pointsNumBeforeLines = pathLayerMesh:CountPoints()
		table.insert(pointList.point, pointsNumBeforeLines)
		table.insert(pointList.color, self.trackingList.color[i])
		local prewPos = LM.Vector2:new_local()
		for p = 1,  #self.trackingList.pos[i].vec2 do
			local pos = self.trackingList.pos[i].vec2[p]
			if p == 1 then
				pathLayerMesh:AddLonePoint(pos, 0)
				prewPos = self.trackingList.pos[i].vec2[p]
			else
				if not self:IsEqual(pos.x, prewPos.x, 0.0001) or not self:IsEqual(pos.y, prewPos.y, 0.0001) then
					pathLayerMesh:AppendPoint(pos, 0)
					if p == 2 and i == 1 then
						local lastPoint = pathLayerMesh:Point(pathLayerMesh:CountPoints()-2)
						lastPoint.fAnimPos:SetValue(1, lastPoint.fAnimPos:GetValue(0))
					end
				end
				prewPos = pos
			end	
		end
		
		local totalPoints = pathLayerMesh:CountPoints()
		
		for c=0, totalPoints - 1 do
			local point = pathLayerMesh:Point(c)
			point:SetCurvature(MOHO.PEAKED, 0)
		end
		pathLayerMesh:SelectConnected()
		local shapeID = moho:CreateShape(false, false, 0)
		local color = self.trackingList.color[i]
		if shapeID >= 0 then
			local shape = pathLayerMesh:Shape(shapeID)	
			local lineWidth = self.width
			lineWidth = LM.Clamp(lineWidth, 0.25, 256)
			shape.fMyStyle.fLineWidth = lineWidth / moho.document:Height()
			shape.fMyStyle.fLineCol:SetValue(0, color)
			shape.fMyStyle.fLineCaps = 0
			shape:MakePlain()
		end
		pathLayerMesh:SelectNone()
		
		if self.drawTimingMark then
			local listLength = #self.trackingList.pos[i].vec2
			local prewPos = LM.Vector2:new_local()
			for p = 1, listLength do
				local frame = self.trackingList.pos[i].frame[p]
				local pos = self.trackingList.pos[i].vec2[p]
				local nextPos = LM.Vector2:new_local()
				local counter = 1
				
				if p == 1 then
					prewPos = self.trackingList.pos[i].vec2[p]
				end
				
				if p < listLength then
					local counterNext = 1
					repeat
						nextPos = self.trackingList.pos[i].vec2[p + counterNext]
						if not self:IsEqual(pos.x, nextPos.x, 0.0001) or not self:IsEqual(pos.y, nextPos.y, 0.0001) then
							break
						end
						counterNext = counterNext + 1
					until p + counterNext > listLength	
				else	
					nextPos = self.trackingList.pos[i].vec2[p]
				end
				if p == 1 then
					local notchStart, notchEnd = self:GetTimingMarkPos(moho, nextPos, pos, nextPos, (self.timingMarksSize / 2) * self.width, true, frame)
					pathLayerMesh:AddLonePoint(notchStart, 0)
					pathLayerMesh:AppendPoint(notchEnd, 0)
				else
					if not self:IsEqual(pos.x, prewPos.x, 0.0001) or not self:IsEqual(pos.y, prewPos.y, 0.0001) then
						local notchStart, notchEnd = self:GetTimingMarkPos(moho, prewPos, pos, nextPos, (self.timingMarksSize / 2) * self.width, false, frame)
						pathLayerMesh:AddLonePoint(notchStart, 0)
						pathLayerMesh:AppendPoint(notchEnd, 0)
						prewPos = pos
					end	
				end	
				pathLayerMesh:SelectNone()
			end
			
			local totalPointsWithtimingMark = pathLayerMesh:CountPoints()
			
			for i = totalPoints, totalPointsWithtimingMark - 1 do
				local point = pathLayerMesh:Point(i)
				point.fSelected = true
			end
			
			local shapeID = moho:CreateShape(false, false, 0)
			if shapeID >= 0 then
				local shape = pathLayerMesh:Shape(shapeID)	
				local lineWidth = self.width / 2
				lineWidth = LM.Clamp(lineWidth, 0.25, 256)
				shape.fMyStyle.fLineWidth = lineWidth / moho.document:Height()
				shape.fMyStyle.fLineCol:SetValue(0, color)
				shape.fMyStyle.fLineCaps = 0	
				shape:MakePlain()
			end
			pathLayerMesh:SelectNone()
		end
	end
	
	if self.centerLine then
		for p = 1, #pointList.point do
			local linePoint = pathLayerMesh:Point(pointList.point[p])
			linePoint.fSelected = true
			pathLayerMesh:SelectConnected()
			local pathColor = pointList.color[p]
			local secondColor = LM.rgb_color:new_local()
			if (pathColor.r + pathColor.g + pathColor.b) < 382 then
				secondColor.r = 255
				secondColor.g = 255
				secondColor.b = 255
				secondColor.a = 255
			else
				secondColor.r = 0
				secondColor.g = 0
				secondColor.b = 0
				secondColor.a = 255
			end
			local v = LM.Vector2:new_local()
			local vc1 = LM.ColorVector:new_local()
			local vc2 = LM.ColorVector:new_local()
			vc1:Set(pathColor)
			vc2:Set(secondColor)
			vc1 = (vc1 + vc2) / 2
			local newColor = vc1:AsColorStruct()
			local shapeID = moho:CreateShape(false, false, 0)
			if shapeID >= 0 then
				local shape = pathLayerMesh:Shape(shapeID)	
				local lineWidth = (self.width / 2) / 2
				lineWidth = LM.Clamp(lineWidth, 0.1, 256)
				shape.fMyStyle.fLineWidth = lineWidth / moho.document:Height()
				shape.fMyStyle.fLineCol:SetValue(0, newColor)
				shape.fMyStyle.fLineCaps = 0
				shape:MakePlain()
			end	
			pathLayerMesh:SelectNone()
		end	
	end
	
	if wireframe then
		moho.view:SetQualityFlags(currentQualityFlags)
	end
	
	MOHO.MohoGlobals.MuteSoundtrack = isSoundtrackMuted
	
	moho.view:DrawMe()
	moho:SetSelLayer(curLayer)
	
	if curAction ~= '' then
		self:ReturnToAction(moho, curAction, curLayer)
	end	
	
	moho:SetCurFrame(curFrame)
	self.isTracking = false
	pathLayer:UpdateCurFrame()
	curLayer:UpdateCurFrame()
	moho:UpdateUI()
	moho.view:DrawMe()
end

function MR_Path:DeletePathLayer(moho)
	self:CleanUpInvalidTargets(moho)
	local pathLayer, targetLayer = self:CheckExistPathLayer(moho)
	if pathLayer then
		moho.document:SetDirty()
		moho.document:PrepUndo()
		local curAction = moho.layer:CurrentAction()
		local curFrame = moho.frame
		local curLayer = moho.layer
		moho:DeleteLayer(pathLayer)
		self:ReturnToAction(moho, curAction, curLayer)
		if curFrame > 0 then
			moho:SetCurFrame(0)
			moho:SetCurFrame(curFrame)
		elseif curFrame == 0 then
			moho:SetCurFrame(1)
			moho:SetCurFrame(curFrame)
		end
		moho.layer:UpdateCurFrame()
		moho.view:DrawMe()
		moho:UpdateUI()
		moho:UpdateUI()
		moho.view:DrawMe()
	end
end

function MR_Path:ClearPath(moho)
	self:CleanUpInvalidTargets(moho)
	local pathLayer, targetLayer = self:CheckExistPathLayer(moho)
	if pathLayer then
		moho.document:SetDirty()
		moho.document:PrepUndo(pathLayer, true)
		pathLayerMesh = moho:LayerAsVector(pathLayer):Mesh()
		pathLayerMesh:Clear()
		pathLayer:UpdateCurFrame()
		moho:UpdateUI()
		moho.view:DrawMe()
	end
end

function MR_Path:CreateNewLayer(moho)
	local pathLayer = moho:CreateNewLayer(MOHO.LT_VECTOR, false)
	local scriptData = pathLayer:ScriptData()
	scriptData:Set("MR Path Layer", true)
	
	self.numVers = {}
	local vers = moho:AppVersion()

	for n in string.gmatch (vers, "%d+") do
		table.insert(self.numVers, tonumber(n))
	end
	
	if self.numVers[1] == 13 and self.numVers[2] == 5 then
		if self.numVers[3] ~= nil then
			if self.numVers[3] >= 2 then
				pathLayer:SetIgnoredByLayerPicker(true)
			end
		end
	elseif self.numVers[1] == 13 and self.numVers[2] > 5 then
		pathLayer:SetIgnoredByLayerPicker(true)
	elseif self.numVers[1] > 13 then
		pathLayer:SetIgnoredByLayerPicker(true)	
	end
	
	pathLayer:SetName('Path')
	pathLayer.fAlpha:SetValue(0, self.pathOpacity / 100)
	pathLayer:SetEditOnly(true)
	pathLayer:SetImmuneToCamera	(true)
	
	return pathLayer
end

function MR_Path:GetLayerByUUID(moho, uuid)
	local count = 0
	repeat
		local layer = moho.document:LayerByAbsoluteID(count)
		if layer then
			count = count + 1
			if layer:UUID() == uuid then
				return layer
			end	
		end
	until not layer
	return nil
end

function MR_Path:GetGlobalPos(moho, layer, pos)
	local globalPos = LM.Vector2:new_local()
	globalPos:Set(pos)
	local layerMatrix = LM.Matrix:new_local()
	layer:GetFullTransform(moho.frame, layerMatrix, moho.document)
	layerMatrix:Transform(globalPos)
	return globalPos
end

function MR_Path:CheckExistPathLayer(moho)
	local topLayer = moho.document:Layer(moho.document:CountLayers()-1)
	local pathLayer = nil
	local targetLayer = nil
	local scriptData = topLayer:ScriptData()
	if (scriptData:HasKey("MR Path Layer")) then
		pathLayer = moho:LayerAsVector(topLayer)
	elseif (scriptData:HasKey("MR Guides Layer")) then
		topLayer = moho.document:Layer(moho.document:CountLayers()-2)
		scriptData = topLayer:ScriptData()
		if (scriptData:HasKey("MR Path Layer")) then
			pathLayer = moho:LayerAsVector(topLayer)
		else
			targetLayer = topLayer
		end	
	elseif (scriptData:HasKey("MR Overlay Layer")) then
		topLayer = moho.document:Layer(moho.document:CountLayers()-1)
		scriptData = topLayer:ScriptData()
		if (scriptData:HasKey("MR Path Layer")) then
			pathLayer = moho:LayerAsVector(topLayer)
		else
			targetLayer = topLayer
		end
	else
		targetLayer = topLayer
	end	
	return pathLayer, targetLayer
end

function MR_Path:GetTimingMarkPos(moho, vec1, vec2, vec3, size, firstMarker, frame)
	local adaptedSize = size / 100
	local v1 = vec1
	local v2 = vec2
	v2 = v2 - v1	
	local angle = math.atan2(v2.y, v2.x)
	
	local v2 = vec2
	local v3 = vec3
	v3 = v3 - v2

	local angle2 = math.atan2(v3.y, v3.x)

	local p1 = vec1
	local p2 = vec2
	local p3 = vec3
	
	local v1 = {x = p1.x - p2.x, y = p1.y - p2.y}
    local v2 = {x = p3.x - p2.x, y = p3.y - p2.y}

    local v1_length = math.sqrt(v1.x^2 + v1.y^2)
    local v2_length = math.sqrt(v2.x^2 + v2.y^2)
    
    local dot_product = v1.x * v2.x + v1.y * v2.y
    
    local cos_angle = dot_product / (v1_length * v2_length)
    
    cos_angle = math.max(-1, math.min(1, cos_angle))
    
    local angle3 = math.acos(cos_angle)
	if not firstMarker then
		if angle3 == 0 or (self:IsEqual(vec2.x, vec3.x, 0.0001) and self:IsEqual(vec2.y, vec3.y, 0.0001)) then
			angle = angle + math.pi / 2
		else
			local delta = angle2 - angle
			
			if delta > math.pi then
				delta = delta - 2 * math.pi
			elseif delta < -math.pi then
				delta = delta + 2 * math.pi
			elseif delta == 0 then
				delta = delta - 2 * math.pi
			end

			if delta > 0 then
				angle = angle - (angle3 / 2)
			elseif delta < 0 then
				angle = angle + (angle3 / 2)
			end
		end
	else
		angle = angle + math.pi / 2
	end

	local vec3 = LM.Vector2:new_local()
    vec3.x = vec2.x + adaptedSize * math.cos(angle)
    vec3.y = vec2.y + adaptedSize * math.sin(angle)

    local vec4 = LM.Vector2:new_local()
    vec4.x = vec2.x + adaptedSize * math.cos(angle + math.pi)
    vec4.y = vec2.y + adaptedSize * math.sin(angle + math.pi)
    return vec3, vec4
end


function MR_Path:IsEqual(a, b, epsilon)
    local absA = math.abs(a)
    local absB = math.abs(b)
    local diff = math.abs(a - b)

    if a == b then 
        return true 
    elseif diff < epsilon then 
        return true
    else 
        return false
    end
end

function MR_Path:ReturnToAction(moho, action, layer)
	if action ~= '' then
		local parentGroup = layer:Parent()
		local skelLayer = nil
		
		if layer:LayerType() == 4 and layer:HasAction(action) then
			skelLayer = layer
		elseif parentGroup ~= nil then
			local targetGroup = parentGroup
			repeat
				if targetGroup:LayerType() == 4 then -- LT_BONE
					if targetGroup:HasAction(action) then
						skelLayer = targetGroup
					end	
				end
				targetGroup = targetGroup:Parent()
			until targetGroup == nil
		end
		
		moho:SetSelLayer(skelLayer)
		moho.document:SetCurrentDocAction(action)
		skelLayer:ActivateAction(action)
		if skelLayer ~= layer then
			moho:SetSelLayer(layer)
			layer:ActivateAction(action)
		end
	else	
		moho:SetSelLayer(layer)
	end
end

function MR_Path:BuildFloatingPanelDialog(moho)
	if MR_Path.floatingPanelDlog == nil then
		MR_Path.floatingPanelDlog = MR_PathFloatingPanelDialog:new()
		if MR_Path.floatingPanelDlog then
			if (MR_Path.floatingPanelDlog:DoModeless() == LM.GUI.MSG_CANCEL) then
				return false
			end
		end
	end
end

function MR_Path:GetMohoVersion(moho)
	local numVers = {}
	local vers = moho:AppVersion()
	for n in string.gmatch (vers, "%d+") do
		table.insert(numVers, tonumber(n))
	end
	return numVers[1], numVers[2], numVers[3]
end

-- **************************************************
-- Localization
-- **************************************************

function MR_Path:Localize(text)
	local phrase = {}

	phrase['Description'] = 'Draw bone and point trajectories over all layers for spacing analysis.'
	phrase['UILabel'] = 'MR Path '..self:Version()

	phrase['Track bone tip'] = 'Track bone tip'
	phrase['Add to tracking'] = 'Add to tracking'
	phrase['Remove from tracking'] = 'Remove from tracking'
	phrase['Clear tracking list'] = 'Clear tracking list'
	phrase['Create path'] = 'Create path'
	phrase['Clear path'] = 'Clear path'
	phrase['Delete path layer'] = 'Delete path layer'
	phrase['Show hide'] = 'Show hide'
	phrase['Path Opacity:'] = 'Opacity:'
	phrase['Opacity for all Paths'] = 'Path layer opacity'
	phrase['Width:'] = 'Width:'
	phrase['Width for all Paths'] = 'Width for all Paths'
	phrase['Color'] = 'Color:'
	phrase['Draw center line'] = 'Draw center line'
	phrase['Draw timing marks'] = 'Draw tick marks'
	phrase['Size:'] = 'Size:'
	phrase['Timing marks size'] = 'Timing marks size'
	phrase['Path Color Tooltip'] = 'Path Color'
	phrase['Adjacent frames range'] = 'Adjacent frames range'
	phrase['Adjacent frames'] = 'Adjacent frames'
	phrase['Playback range'] = 'Playback range'
	phrase['Project range'] = 'Project range'
	phrase['From to range'] = 'From/To range'
	phrase['From'] = 'From'
	phrase['To'] = 'To'
	phrase['IntervalText'] = 'Interval:'
	phrase['Panel settings'] = 'Panel settings'
	phrase['Open in panel'] = 'Open in panel'
	
	phrase['Compact'] = 'Compact'
	phrase['Mid'] = 'Mid'
	phrase['Full'] = 'Full'
	
	phrase['Path Target Manager'] = 'Path Target Manager'
	phrase['Path target list is empty'] = 'Path target list is empty'
	phrase['Reset settings'] = 'Click to reset settings. Alt + Click to copy settings to other targets.'
	phrase['Settings changed'] = 'The settings of this target have been changed.'
	phrase['Settings not changed'] = 'The settings of this target have not been changed.'
	phrase['Randomize colors'] = 'Randomize colors'
	
	phrase['Targets limit exceeded alert 1'] = 'Targets limit exceeded.'
	phrase['Targets limit exceeded alert 2'] = 'You have added the maximum number of targets ('..self.maxTargets..'). Number of targets not added:'

	return phrase[text]
end

Icon
MR Path
Listed

Script type: Tool

Uploaded: Jun 25 2023, 07:52

Last modified: Feb 10 2024, 13:18

Draw bone and point trajectories over all layers for spacing analysis.


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