--import config.lua
--import utils.lua
--import uiLib.lua
--import polyUtil.lua

AnimEditor = {}

AnimEditor.screenWidth = 340.0
AnimEditor.screenHeight = 320.0
AnimEditor.screenSize = vec2(AnimEditor.screenWidth, AnimEditor.screenHeight)

AnimEditor.assetName = ""
AnimEditor.modelFilename = ""
AnimEditor.dirty = false

CAMERA_PERSPECTIVE = 1
CAMERA_ORTHO = 2
AnimEditor.cameraType = CAMERA_PERSPECTIVE

AnimEditor.camFov = degToRad(35.0)
AnimEditor.camMat = mat4()
AnimEditor.camManip = {pos = vec3(0.0, 0.0, 5.0), rot = eulerRot(0.0, 0.0, 0.0), focusDist = 5.0, ortho = false}
AnimEditor.orthoCamZoomMin = 0.05
AnimEditor.orthoCamZoomMax = 2.0

AnimEditor.mousePos = vec2(0.0, 0.0)
AnimEditor.mousePressed = -1
AnimEditor.mouseReleased = -1
AnimEditor.mouseDown = -1
AnimEditor.mousePressedPos = vec2(0.0, 0.0)
AnimEditor.manipulatingCamera = false
AnimEditor.mouseClickStartedOutside = false
AnimEditor.mousePressedOnUI = false
AnimEditor.mouseInViewport = false

AnimEditor.currentPageId = 1
AnimEditor.meshes = {}
AnimEditor.meshBoundingBoxes = {}
AnimEditor.meshTransforms = {}
AnimEditor.hierarchy = {}
AnimEditor.rootModel = nil
AnimEditor.hoveredMesh = -1
AnimEditor.selectedMesh = -1
AnimEditor.meshModels = {}
AnimEditor.propsEnabled = false
AnimEditor.waves = {}

AnimEditor.propValues = {0.0, 1.5, -0.5, 45.0, 0.0, 90.0, 1.0, 1.0, 1.0}
AnimEditor.waveFreqValues = {1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0}
AnimEditor.waveAmpValues = {1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0}
AnimEditor.wavePropEnabled = {false, false, false, false, false, false, false, false, false}
AnimEditor.draggingTimeline = false
AnimEditor.isPlaying = false
AnimEditor.currentTime = 0.0
AnimEditor.currentFrame = 0
AnimEditor.keyframesData = {}
AnimEditor.undoStack = {}
AnimEditor.undoStackId = 0
AnimEditor.pagesData = {}

AnimEditor.bilinearInterp = false
AnimEditor.ditherEnabled = false

AnimEditor.shadingType = 1

function _setAssetName(assetName)
	AnimEditor.assetName = assetName
	_updateViewTitle()
end

function _setAnimPlaying(state)
	if state == AnimEditor.isPlaying then
		return
	end

	AnimEditor.isPlaying = not AnimEditor.isPlaying
	if AnimEditor.isPlaying then
		AnimEditor.playButton.iconSpriteData.color = Config.paletteNames.greenLight
		for i = 1, #AnimEditor.propSliders do
			AnimEditor.propSliders[i].field.hoverEnabled = false
		end
	else
		AnimEditor.playButton.iconSpriteData.color = Config.uiColors.icon
		if AnimEditor.propsEnabled then
			for i = 1, #AnimEditor.propSliders do
				AnimEditor.propSliders[i].field.hoverEnabled = true
			end
		end
	end
	setSprite(AnimEditor.playButton.iconSprite, AnimEditor.playButton.iconSpriteData)
end

function _togglePlay()
	_setAnimPlaying(not AnimEditor.isPlaying)
end

function _playAnim()
	local nextFrame = AnimEditor.currentFrame + 1
	if nextFrame >= AnimEditor.framesCount then
		nextFrame = 0
	end
	_setCurrentFrame(nextFrame)
end

function _updateViewTitle()
	local programId = getProgramId()
	local topBarTitle = "anim editor"
	if AnimEditor.assetName ~= "" then
		topBarTitle = "anim editor: " .. AnimEditor.assetName
	elseif AnimEditor.modelFilename ~= "" then
		topBarTitle = "anim editor: " .. AnimEditor.modelFilename
	end
	if AnimEditor.dirty and AnimEditor.assetName ~= "" then
		topBarTitle = topBarTitle .. " *"
	end

	invokeProgram(MAIN_PROGRAMID, "setViewTitle", programId, topBarTitle)
end

function _updateWaveFieldState(idx)
	local enabled = AnimEditor.wavePropEnabled[idx]
	local freqField = AnimEditor.waveProps[idx].freqSlider.field
	local ampField = AnimEditor.waveProps[idx].ampSlider.field

	if enabled then
		freqField.hoverEnabled = true
		freqField.colorBase = Config.uiColors.animEditorWaveField
		freqField.color = Config.uiColors.animEditorWaveField
		freqField.colorHover = Config.uiColors.animEditorWaveField + 10
		freqField.textColor = Config.uiColors.animEditorWaveFieldText

		ampField.hoverEnabled = true
		ampField.colorBase = Config.uiColors.animEditorWaveField
		ampField.color = Config.uiColors.animEditorWaveField
		ampField.colorHover = Config.uiColors.animEditorWaveField + 10
		ampField.textColor = Config.uiColors.animEditorWaveFieldText
	else
		freqField.hoverEnabled = false
		freqField.colorBase = Config.uiColors.animEditorWaveField * 0.01
		freqField.color = Config.uiColors.animEditorWaveField * 0.01
		freqField.textColor = Config.uiColors.animEditorWaveFieldText * 0.1

		ampField.hoverEnabled = false
		ampField.colorBase = Config.uiColors.animEditorWaveField * 0.01
		ampField.color = Config.uiColors.animEditorWaveField * 0.01
		ampField.textColor = Config.uiColors.animEditorWaveFieldText * 0.1
	end

	freqField.changed = true
	ampField.changed = true
end

function _newMenuPressed()
	UILib.openModal(AnimEditor.screenWidth, AnimEditor.screenHeight, "all unsaved changes will be lost",
		"ok", function()
			_newAnim()
		end,
		"cancel", function() UILib.closeModal() end)
end

function _newAnim()
	_setAnimPlaying(false)
	
	if AnimEditor.rootModel ~= nil then
		destroy(AnimEditor.rootModel)
	end

	AnimEditor.meshes = {}
	AnimEditor.meshBoundingBoxes = {}
	AnimEditor.meshTransforms = {}
	AnimEditor.meshModels = {}
	AnimEditor.hierarchy = {}
	AnimEditor.rootModel = nil
	AnimEditor.modelFilename = ""
	AnimEditor.hoveredMesh = -1
	AnimEditor.selectedMesh = -1
	AnimEditor.waves = {}
	AnimEditor.keyframesData = {}
	AnimEditor.undoStack = {}
	AnimEditor.undoStackId = 0
	_storeUndoState()
	AnimEditor.dirty = false
	_setAssetName("")
	_setPropsEnabled(false)
	AnimEditor.hierarchyInspector:inspect({})
	_refreshTimelineKeyframes()
end

function _saveNewMenuPressed()
	local newName = _findNewAnimName()
	_setAssetName(newName)
	save()
end

function _buildUI()
	local offset = 0
	local label = "file"
	local fileMenuButton = UILib.button(label, vec3(0, 0, 0.0), vec2(8 * #label, 8), Config.uiColors.panel)
	fileMenuButton.callback = function()
		UILib.openMenu(fileMenuButton, {
			{"new", _newMenuPressed},
			{"save", save},
			{"save new", _saveNewMenuPressed},
		})
	end

	offset = offset + 8 * #label + 8
	label = "edit"
	local editMenuButton = UILib.button(label, vec3(offset, 0, 0.0), vec2(8 * #label, 8), Config.uiColors.panel)
	editMenuButton.callback = function()
		UILib.openMenu(editMenuButton, {
			{"undo", function() _undo() end},
			{"redo", function() _redo() end},
		})
	end

	offset = offset + 8 * #label + 8
	label = "p"
	AnimEditor.cameraPerspButton = UILib.button(label, vec3(offset, 0, 0.0), vec2(8 * #label, 8), Config.uiColors.panel)
	AnimEditor.cameraPerspButton.textColor = Config.uiColors.panelLight
	AnimEditor.cameraPerspButton.changed = true
	AnimEditor.cameraPerspButton.callback = function() AnimEditor.switchCameraPerspective() end
	AnimEditor.cameraPerspButton.desc = "persp/ortho camera"

	offset = offset + 8 * #label + 8

	AnimEditor.shade4Button = UILib.buttonIcon(
		vec3(offset + (8.0 * 2), 0.0, 0.0), vec2(8.0, 8.0),
		Config.uiColors.panel,
		vec2(0.0, 0.0), vec2(8.0, 8.0),
		vec2(8.0 * 15.0, 8.0 * 5.0), vec2(8.0, 8.0),
		Config.uiColors.icon, function() AnimEditor.setShadingType(4) end)
	AnimEditor.shade4Button.roundCorner = 0.3
	AnimEditor.shade4Button.desc = "texture unlit"

	AnimEditor.shade1Button = UILib.buttonIcon(
		vec3(offset + 8.0, 0.0, 0.0), vec2(8.0, 8.0),
		Config.uiColors.panel,
		vec2(0.0, 0.0), vec2(8.0, 8.0),
		vec2(8.0 * 15.0, 0.0), vec2(8.0, 8.0),
		Config.uiColors.icon, function() AnimEditor.setShadingType(1) end)
	AnimEditor.shade1Button.roundCorner = 0.3
	AnimEditor.shade1Button.desc = "texture shaded"

	AnimEditor.shade2Button = UILib.buttonIcon(
		vec3(offset, 0.0, 0.0), vec2(8.0, 8.0),
		Config.uiColors.panel,
		vec2(0.0, 0.0), vec2(8.0, 8.0),
		vec2(8.0 * 14.0, 0.0), vec2(8.0, 8.0),
		Config.uiColors.icon, function() AnimEditor.setShadingType(2) end)
	AnimEditor.shade2Button.roundCorner = 0.3
	AnimEditor.shade2Button.desc = "shaded"

	AnimEditor.backgroundSprite = allocSprite()
	AnimEditor.backgroundSpriteData = {}
	AnimEditor.backgroundSpriteData.position = vec3(0.0, 0.0, UILib.uiDepth_views - 1)
	AnimEditor.backgroundSpriteData.size = vec2(AnimEditor.screenWidth, AnimEditor.screenHeight)
	AnimEditor.backgroundSpriteData.color = Config.uiColors.animEditorBackground
	setSprite(AnimEditor.backgroundSprite, AnimEditor.backgroundSpriteData)

	local hierarchyWidth = 8 * 11
	local hierarchyHeight = 8 * 19
	local hierarchyX = AnimEditor.screenWidth - hierarchyWidth
	local hierarchyY = 8

	AnimEditor.hierarchyInspector = UILib.hierarchyInspector()
	AnimEditor.hierarchyInspector.position.x = hierarchyX
	AnimEditor.hierarchyInspector.position.y = hierarchyY
	AnimEditor.hierarchyInspector.size.x = hierarchyWidth
	AnimEditor.hierarchyInspector.size.y = hierarchyHeight
	AnimEditor.hierarchyInspector:setVisible(true)
	AnimEditor.hierarchyInspector.enableHierarchyChange = false
	AnimEditor.hierarchyInspector.enableRename = false
	AnimEditor.hierarchyInspector.nodeClickCallback = _onHierarchyNodeClicked

	local viewX = 1.0
	local viewY = 8.0
	local viewWidth = AnimEditor.screenWidth - hierarchyWidth - 2.0
	local viewHeight = AnimEditor.screenHeight - 8.0 - 16.0 - 8.0 - 1.0

	AnimEditor.viewSpriteData = {}
	AnimEditor.viewSpriteData.position = vec3(viewX, viewY, 0.0)
	AnimEditor.viewSpriteData.size = vec2(viewWidth, viewHeight)
	AnimEditor.viewSpriteData.color = Config.uiColors.levelEditorViewport
	AnimEditor.viewSpriteData.border = 0.0
	AnimEditor.viewSprite = allocSprite()
	setSprite(AnimEditor.viewSprite, AnimEditor.viewSpriteData)

	local propLabels = {"posX", "posY", "posZ", "rotX", "rotY", "rotZ", "sclX", "sclY", "sclZ"}
	local propDesc = {"position X", "position Y", "position Z", "rotation X", "rotation Y", "rotation Z", "scale X", "scale Y", "scale Z"}
	local propX = hierarchyX
	local propY = hierarchyY + hierarchyHeight
	local propLabelWidth = hierarchyWidth
	local propLabelHeight = 8.0
	local propSpacing = 8.0

	local propLabelTextWidth = 8 * 4
	local propFieldWidth = propLabelWidth - propLabelTextWidth
	local axisColors = {Config.paletteNames.red, Config.paletteNames.green, Config.paletteNames.blue}

	local wavePropButtonSize = 8.0

	AnimEditor.propSliders = {}
	AnimEditor.waveProps = {}
	for i = 1, #propLabels do
		local rowY = propY + (i - 1) * (propLabelHeight + wavePropButtonSize)
		local axisColor = axisColors[((i - 1) % 3) + 1]

		local idx = i
		AnimEditor.propSliders[i] = UILib.slider(propLabels[i], vec3(propX, rowY, 0.0), vec2(propLabelTextWidth, propLabelHeight), vec2(propFieldWidth - 8.0, propLabelHeight), {
			sensitivity = 0.1,
			defaultValue = AnimEditor.propValues[i],
			gap = 8.0,
			fieldOffset = vec2(propLabelTextWidth + 8.0, 0.0),
			labelColor = Config.uiColors.panel,
			labelTextColor = axisColor,
			fieldColor = axisColor * 0.5,
			fieldColorHover = axisColor + 10,
			fieldTextColor = Config.uiColors.animEditorPropFieldText,
			fieldTextColorActive = Config.uiColors.animEditorPropFieldTextActive,
			onChange = function(slider, newValue)
				AnimEditor.propValues[idx] = newValue
				_applyPropsToModel()
			end,
			onRelease = function(slider, finalValue)
				_storeUndoState()
			end,
			onTextEdit = function(slider, newValue)
				if AnimEditor.isPlaying then return end
				AnimEditor.propValues[idx] = newValue
				_applyPropsToModel()
				_storeUndoState()
			end,
		})
		AnimEditor.propSliders[i].field.desc = propDesc[i]

		local waveButton = UILib.buttonIcon(
			vec3(propX, rowY + propLabelHeight, 0.0), vec2(wavePropButtonSize, wavePropButtonSize),
			Config.uiColors.panel,
			vec2(0.0, 0.0), vec2(8.0, 8.0),
			vec2(8 * 4, 8 * 7), vec2(8.0, 8.0),
			Config.uiColors.icon, function()
				if AnimEditor.waveProps[idx].hoverEnabled == false then
					return
				end

				AnimEditor.wavePropEnabled[idx] = not AnimEditor.wavePropEnabled[idx]
				UILib.setButtonPressed(AnimEditor.waveProps[idx], AnimEditor.wavePropEnabled[idx])
				_updateWaveFieldState(idx)
				_syncWaveToModel(idx)
			end)
		waveButton.roundCorner = 0.5
		waveButton.desc = "enable sine wave"
		AnimEditor.waveProps[i] = waveButton

		local waveFieldX = propX + wavePropButtonSize + 2.0
		local waveFieldWidth = (propLabelWidth - wavePropButtonSize - 2.0) * 0.5
		local waveFieldY = rowY + propLabelHeight

		AnimEditor.waveProps[i].freqSlider = UILib.slider("", vec3(waveFieldX, waveFieldY, 0.0), vec2(0, 0), vec2(waveFieldWidth - 1.0, wavePropButtonSize), {
			sensitivity = 0.1,
			defaultValue = AnimEditor.waveFreqValues[i],
			fieldColor = Config.uiColors.animEditorWaveField,
			fieldColorHover = Config.uiColors.animEditorWaveField + 10,
			fieldTextColor = Config.uiColors.animEditorWaveFieldText,
			onChange = function(slider, newValue)
				AnimEditor.waveFreqValues[idx] = newValue
				_syncWaveToModel(idx)
			end,
			onRelease = function(slider, finalValue)
				_storeUndoState()
			end,
			onTextEdit = function(slider, newValue)
				AnimEditor.waveFreqValues[idx] = newValue
				_syncWaveToModel(idx)
				_storeUndoState()
			end,
		})
		AnimEditor.waveProps[i].freqSlider.field.desc = "sine wave frequency"

		AnimEditor.waveProps[i].ampSlider = UILib.slider("", vec3(waveFieldX + waveFieldWidth + 1.0, waveFieldY, 0.0), vec2(0, 0), vec2(waveFieldWidth - 1.0, wavePropButtonSize), {
			sensitivity = 0.1,
			defaultValue = AnimEditor.waveAmpValues[i],
			fieldColor = Config.uiColors.animEditorWaveField,
			fieldColorHover = Config.uiColors.animEditorWaveField + 10,
			fieldTextColor = Config.uiColors.animEditorWaveFieldText,
			onChange = function(slider, newValue)
				AnimEditor.waveAmpValues[idx] = newValue
				_syncWaveToModel(idx)
			end,
			onRelease = function(slider, finalValue)
				_storeUndoState()
			end,
			onTextEdit = function(slider, newValue)
				AnimEditor.waveAmpValues[idx] = newValue
				_syncWaveToModel(idx)
				_storeUndoState()
			end,
		})
		AnimEditor.waveProps[i].ampSlider.field.desc = "sine wave amplitude"

		_updateWaveFieldState(i)
	end

	AnimEditor.controlBarSprite = allocSprite()
	AnimEditor.controlBarSpriteData = {}
	AnimEditor.controlBarSpriteData.position = vec3(viewX - 1, viewY + viewHeight + 1, 0.1)
	AnimEditor.controlBarSpriteData.size = vec2(viewWidth + 1, 8.0)
	AnimEditor.controlBarSpriteData.color = Config.uiColors.animEditorControlBar
	setSprite(AnimEditor.controlBarSprite, AnimEditor.controlBarSpriteData)

	AnimEditor.playButton = UILib.buttonIcon(
		vec3(viewX - 1, viewY + viewHeight + 1, 0.2), vec2(8.0, 8.0),
		Config.uiColors.animEditorControlBar,
		vec2(0.0, 0.0), vec2(8.0, 8.0),
		vec2(8.0 * 4.0, 8.0 * 6.0), vec2(8.0, 8.0),
		Config.uiColors.icon, function() _togglePlay() end)

	local timestampWidth = 8 * 9
	AnimEditor.timestampSprite = allocSprite()
	AnimEditor.timestampSpriteData = {}
	AnimEditor.timestampSpriteData.position = vec3(viewX + viewWidth - timestampWidth, viewY + viewHeight + 1, 0.2)
	AnimEditor.timestampSpriteData.size = vec2(timestampWidth, 8.0)
	AnimEditor.timestampSpriteData.color = Config.uiColors.animEditorControlBar
	AnimEditor.timestampSpriteData.text = "0.00 #00"
	AnimEditor.timestampSpriteData.textColor = Config.uiColors.animEditorControlBarTimestamp
	setSprite(AnimEditor.timestampSprite, AnimEditor.timestampSpriteData)

	AnimEditor.timelineSprite = allocSprite()
	AnimEditor.timelineSpriteData = {}
	AnimEditor.timelineSpriteData.position = vec3(0.0, AnimEditor.screenHeight - 16.0, 0.0)
	AnimEditor.timelineSpriteData.size = vec2(AnimEditor.screenWidth, 16.0)
	AnimEditor.timelineSpriteData.color = Config.uiColors.animEditorTimelineBackground
	setSprite(AnimEditor.timelineSprite, AnimEditor.timelineSpriteData)

	local framesCount = math.floor(AnimEditor.screenWidth / 4)
	
	AnimEditor.timelineFrames = {}
	for i = 1, framesCount do
		local tick = {}
		tick.sprite = allocSprite()
		tick.position = vec3((i - 1) * 4, AnimEditor.timelineSpriteData.position.y, 0.1)
		tick.size = vec2(3.0, AnimEditor.timelineSpriteData.size.y)
		tick.color = Config.uiColors.animEditorTimelineFrame
		setSprite(tick.sprite, tick)
		AnimEditor.timelineFrames[i] = tick
	end

	AnimEditor.framesCount = framesCount

	AnimEditor.currentTimeSprite = allocSprite()
	AnimEditor.currentTimeSpriteData = {}
	AnimEditor.currentTimeSpriteData.position = vec3(0.0, AnimEditor.timelineSpriteData.position.y, 0.2)
	AnimEditor.currentTimeSpriteData.size = vec2(3.0, AnimEditor.timelineSpriteData.size.y)
	AnimEditor.currentTimeSpriteData.color = Config.uiColors.animEditorCurrentFrame
	setSprite(AnimEditor.currentTimeSprite, AnimEditor.currentTimeSpriteData)

	AnimEditor.descPanel = UILib.panel("", vec3(0.0, AnimEditor.screenHeight - 8, 1.0), vec2(AnimEditor.screenWidth, 8.0), Config.uiColors.descPanel)
	AnimEditor.descPanel.visible = false
	AnimEditor.descPanel.changed = true
	AnimEditor.statusText = ""

	AnimEditor.playButton.desc = "play/stop anim"

	_updateViewTitle()

	AnimEditor.shadingTypeChanged()
end

function AnimEditor.setShadingType(shadingType)
	AnimEditor.shadingType = shadingType
	AnimEditor.shadingTypeChanged()

	Utils.setAppVar("animEditor_shadingType", shadingType)
end

function AnimEditor.shadingTypeChanged()
	AnimEditor.shade4Button.iconSpriteData.color = vec4Copy(Config.uiColors.panelLight)
	AnimEditor.shade1Button.iconSpriteData.color = vec4Copy(Config.uiColors.panelLight)
	AnimEditor.shade2Button.iconSpriteData.color = vec4Copy(Config.uiColors.panelLight)

	if AnimEditor.shadingType == 1 then
		AnimEditor.shade1Button.iconSpriteData.color = vec4Copy(Config.uiColors.icon)
	elseif AnimEditor.shadingType == 2 then
		AnimEditor.shade2Button.iconSpriteData.color = vec4Copy(Config.uiColors.icon)
	elseif AnimEditor.shadingType == 4 then
		AnimEditor.shade4Button.iconSpriteData.color = vec4Copy(Config.uiColors.icon)
	end

	setSprite(AnimEditor.shade4Button.iconSprite, AnimEditor.shade4Button.iconSpriteData)
	setSprite(AnimEditor.shade1Button.iconSprite, AnimEditor.shade1Button.iconSpriteData)
	setSprite(AnimEditor.shade2Button.iconSprite, AnimEditor.shade2Button.iconSpriteData)
end

function start()
	UILib.init()

	_buildUI()
	_setPropsEnabled(false)

	local programId = getProgramId()
	invokeProgram(MAIN_PROGRAMID, "registerShortcut", {desc = "undo", key = KEY_Z, modifier = KEY_LEFT_CONTROL, callback = "shortcutUndo", programId = programId})
	invokeProgram(MAIN_PROGRAMID, "registerShortcut", {desc = "redo", key = KEY_Y, modifier = KEY_LEFT_CONTROL, callback = "shortcutRedo", programId = programId})
	invokeProgram(MAIN_PROGRAMID, "registerShortcut", {desc = "redo", key = KEY_Z, modifier = {KEY_LEFT_CONTROL, KEY_LEFT_SHIFT}, callback = "shortcutRedo", programId = programId})
	Utils.registerShortcut("play/stop anim", "shortcutPlay", "SPACE", nil, Config)
	Utils.registerShortcut("frame model", "shortcutFrameModel", "F", nil, Config)

	_setCameraType(Utils.getAppVar("cameraType") or CAMERA_PERSPECTIVE)

	_storeUndoState()
end

function clearProgram()
	_newAnim()
end

function programActive(state)
	if state == false then return end

	AnimEditor.bilinearInterp = invokeProgram(MAIN_PROGRAMID, "getProjectVar", "video_bilinearInterpEnabled")
	AnimEditor.ditherEnabled = invokeProgram(MAIN_PROGRAMID, "getProjectVar", "video_ditherEnabled")
	
	_setAnimPlaying(false)

	if AnimEditor.modelFilename ~= nil and AnimEditor.modelFilename ~= "" then
		_loadModel(AnimEditor.modelFilename)
	end

	local name = AnimEditor.assetName
	if name == "" then
		name = AnimEditor.modelFilename
	end
	if name ~= "" then
		invokeProgram(MAIN_PROGRAMID, "selectAsset", name)
	end

	AnimEditor.setShadingType(Utils.getAppVar("animEditor_shadingType") or 2)
end

function _collectMeshes(model, hierarchyEntry)
	local mesh = getModelMesh(model)
	local hasMesh = false

	if mesh ~= nil then
		AnimEditor.meshes[#AnimEditor.meshes + 1] = mesh
		local worldMat = getModelWorldMatrix(model)
		setMeshMicroShader(mesh, "modelMat", worldMat, MICROSHADER_UNIFORM)
		setMeshTexture(mesh, AnimEditor.currentPageId)
		AnimEditor.meshBoundingBoxes[#AnimEditor.meshes] = computeWorldBoundingBox(mesh)
		AnimEditor.meshTransforms[#AnimEditor.meshes] = worldMat
		AnimEditor.meshModels[#AnimEditor.meshes] = model
		hierarchyEntry.meshIndex = #AnimEditor.meshes
		hasMesh = true
	end

	local modelName = getModelName(model)
	hierarchyEntry.name = modelName
	hierarchyEntry.children = {}

	if AnimEditor.waves[modelName] == nil then
		local waveProps = {}
		for i = 1, 9 do
			waveProps[i] = {enabled = false, frequency = 1.0, amplitude = 1.0}
		end
		AnimEditor.waves[modelName] = waveProps
	end

	local children = getModelChildren(model)
	for i = 1, #children do
		local childEntry = {}
		hierarchyEntry.children[#hierarchyEntry.children + 1] = childEntry
		if _collectMeshes(children[i], childEntry) then
			hasMesh = true
		end
	end

	hierarchyEntry.hasMesh = hasMesh
	return hasMesh
end

function _pruneHierarchy(entries)
	local pruned = {}
	for i = 1, #entries do
		local entry = entries[i]
		if entry.hasMesh then
			entry.children = _pruneHierarchy(entry.children)
			pruned[#pruned + 1] = entry
		end
	end
	return pruned
end

function _deepCopyWaves()
	local copy = {}
	for name, props in pairs(AnimEditor.waves) do
		local propsCopy = {}
		for i = 1, #props do
			propsCopy[i] = {enabled = props[i].enabled, frequency = props[i].frequency, amplitude = props[i].amplitude}
		end
		copy[name] = propsCopy
	end
	return copy
end

function setPage(pageId)
	if pageId == AnimEditor.currentPageId then
		return
	end

	_setAnimPlaying(false)

	local pageData = AnimEditor.pagesData[AnimEditor.currentPageId]
	if pageData == nil then
		pageData = {}
		AnimEditor.pagesData[AnimEditor.currentPageId] = pageData
	end

	pageData.assetName = AnimEditor.assetName
	pageData.modelFilename = AnimEditor.modelFilename
	pageData.dirty = AnimEditor.dirty
	pageData.keyframesData = _deepCopyKeyframesData()
	pageData.waves = _deepCopyWaves()
	pageData.undoStack = AnimEditor.undoStack
	pageData.undoStackId = AnimEditor.undoStackId
	pageData.currentFrame = AnimEditor.currentFrame
	pageData.currentTime = AnimEditor.currentTime
	
	pageData.modelCount = #AnimEditor.meshModels
	pageData.modelMatrices = {}
	for i = 1, #AnimEditor.meshModels do
		local model = AnimEditor.meshModels[i]
		if model ~= nil then
			pageData.modelMatrices[i] = getModelMatrix(model)
		end
	end

	if AnimEditor.rootModel ~= nil then
		destroy(AnimEditor.rootModel)
	end

	AnimEditor.currentPageId = pageId

	local newPageData = AnimEditor.pagesData[pageId]

	if newPageData == nil or (newPageData.modelFilename == nil or newPageData.modelFilename == "") then
		AnimEditor.meshes = {}
		AnimEditor.meshBoundingBoxes = {}
		AnimEditor.meshTransforms = {}
		AnimEditor.meshModels = {}
		AnimEditor.hierarchy = {}
		AnimEditor.rootModel = nil
		AnimEditor.modelFilename = ""
		AnimEditor.hoveredMesh = -1
		AnimEditor.selectedMesh = -1
		AnimEditor.dirty = false
		AnimEditor.waves = {}
		AnimEditor.keyframesData = {}
		AnimEditor.undoStack = {}
		AnimEditor.undoStackId = 0
		_storeUndoState()
		_setAssetName("")
		_setPropsEnabled(false)
		AnimEditor.hierarchyInspector:inspect({})
		_refreshTimelineKeyframes()
	else
		AnimEditor.meshes = {}
		AnimEditor.meshBoundingBoxes = {}
		AnimEditor.meshTransforms = {}
		AnimEditor.meshModels = {}
		AnimEditor.hierarchy = {}

		local rootModel = loadModel(newPageData.modelFilename, pageId)[1]
		AnimEditor.rootModel = rootModel
		AnimEditor.modelFilename = newPageData.modelFilename

		local childModels = getModelChildren(rootModel)
		for i = 1, #childModels do
			AnimEditor.hierarchy[i] = {}
			_collectMeshes(childModels[i], AnimEditor.hierarchy[i])
		end

		AnimEditor.hierarchy = _pruneHierarchy(AnimEditor.hierarchy)

		for i = 1, newPageData.modelCount do
			local model = AnimEditor.meshModels[i]
			if model ~= nil and newPageData.modelMatrices[i] ~= nil then
				setModelMatrix(model, newPageData.modelMatrices[i])
				local mesh = AnimEditor.meshes[i]
				if mesh ~= nil then
					local worldMat = getModelWorldMatrix(model)
					setMeshMicroShader(mesh, "modelMat", worldMat, MICROSHADER_UNIFORM)
					AnimEditor.meshBoundingBoxes[i] = computeWorldBoundingBox(mesh)
					AnimEditor.meshTransforms[i] = worldMat
				end
			end
		end

		for name, props in pairs(newPageData.waves) do
			AnimEditor.waves[name] = {}
			for i = 1, #props do
				AnimEditor.waves[name][i] = {enabled = props[i].enabled, frequency = props[i].frequency, amplitude = props[i].amplitude}
			end
		end

		AnimEditor.keyframesData = {}
		for name, keyframes in pairs(newPageData.keyframesData) do
			local kfCopy = {}
			for i = 1, #keyframes do
				local entryCopy = {}
				for j = 1, #keyframes[i] do
					entryCopy[j] = keyframes[i][j]
				end
				kfCopy[i] = entryCopy
			end
			AnimEditor.keyframesData[name] = kfCopy
		end

		AnimEditor.selectedMesh = -1
		AnimEditor.hoveredMesh = -1
		AnimEditor.dirty = newPageData.dirty
		AnimEditor.undoStack = newPageData.undoStack
		AnimEditor.undoStackId = newPageData.undoStackId
		_setAssetName(newPageData.assetName)
		AnimEditor.hierarchyInspector:inspect(AnimEditor.hierarchy)
		_setPropsEnabled(false)
		_refreshTimelineKeyframes()
		_setCurrentFrame(newPageData.currentFrame)

		_updateSelection()
	end
end

function _loadModel(modelName)
	AnimEditor.meshes = {}
	AnimEditor.meshBoundingBoxes = {}
	AnimEditor.meshTransforms = {}
	AnimEditor.meshModels = {}
	AnimEditor.hierarchy = {}
	AnimEditor.hoveredMesh = -1
	AnimEditor.selectedMesh = -1
	AnimEditor.waves = {}

	AnimEditor.currentPageId = invokeProgram(MAIN_PROGRAMID, "getCurrentPage")

	local models = loadModel(modelName, AnimEditor.currentPageId)
	if models == nil or #models == 0 then return end

	local rootModel = models[1]
	AnimEditor.rootModel = rootModel
	AnimEditor.modelFilename = modelName

	local childModels = getModelChildren(rootModel)
	for i = 1, #childModels do
		AnimEditor.hierarchy[i] = {}
		_collectMeshes(childModels[i], AnimEditor.hierarchy[i])
	end

	AnimEditor.hierarchy = _pruneHierarchy(AnimEditor.hierarchy)
	AnimEditor.hierarchyInspector:inspect(AnimEditor.hierarchy)
	_setPropsEnabled(false)
end

function _frameModel()
	if AnimEditor.rootModel == nil then return end

	local bb = computeWorldBoundingBox(AnimEditor.rootModel)
	local minPoint = bb.min
	local maxPoint = bb.max

	local size = vec3Length(maxPoint - minPoint)
	if size < 0.001 then
		size = 1.0
	end

	local center = vec3Lerp(maxPoint, minPoint, 0.5)
	local camManip = AnimEditor.camManip
	local forward = vec3(0.0, 0.0, 1.0) * camManip.rot

	if camManip.ortho then
		camManip.pos = center + (forward * 10.0)
		camManip.focusDist = clamp((1.0 / size) * 0.5, AnimEditor.orthoCamZoomMin, AnimEditor.orthoCamZoomMax)
		camManip.perspFocusDist = 10.0
	else
		camManip.pos = center + (forward * (size * 1.7))
		camManip.focusDist = vec3Length(camManip.pos - center)
	end
end

function shortcutFrameModel()
	_frameModel()
end

function openAsset(assetName)
	local tokens = splitStr(assetName, ".")
	local fileType = tokens[#tokens]

	if fileType == "anim" then
		AnimEditor.currentPageId = invokeProgram(MAIN_PROGRAMID, "getCurrentPage")
		local animData = loadAnim(assetName, AnimEditor.currentPageId)
		if animData == nil then return end

		if animData.modelFilename ~= nil and animData.modelFilename ~= AnimEditor.modelFilename then
			_loadModel(animData.modelFilename)
		end

		AnimEditor.keyframesData = {}
		for name, modelData in pairs(animData) do
			if type(modelData) == "table" and name ~= "modelFilename" then
				if modelData.keyframes ~= nil then
					AnimEditor.keyframesData[name] = modelData.keyframes
				end
				if modelData.waves ~= nil then
					AnimEditor.waves[name] = modelData.waves
				end
			end
		end

		_setAssetName(assetName)
		AnimEditor.dirty = false
		_refreshTimelineKeyframes()
		_applyAnimPose()
		_frameModel()
		AnimEditor.undoStack = {}
		AnimEditor.undoStackId = 0
		_storeUndoState()
	else
		_loadModel(assetName)
		_frameModel()
		_setAssetName("")
		AnimEditor.dirty = false
		AnimEditor.keyframesData = {}
		AnimEditor.undoStack = {}
		AnimEditor.undoStackId = 0
		_storeUndoState()
	end
end

function _findNewAnimName()
	local pageId = invokeProgram(MAIN_PROGRAMID, "getCurrentPage")
	local files = listFiles("page" .. pageId)

	for i = 1, 32 do
		local fileExists = false
		for fileIt = 1, #files do
			if files[fileIt] == "an" .. i .. ".anim" then
				fileExists = true
				break
			end
		end
		if fileExists == false then
			return "an" .. i .. ".anim"
		end
	end

	return "an1.anim"
end

function save()
	if AnimEditor.modelFilename == "" then return end

	if AnimEditor.assetName == "" then
		_setAssetName(_findNewAnimName())
	end

	local models = {}
	local allNames = {}
	for name, _ in pairs(AnimEditor.keyframesData) do allNames[name] = true end
	for name, _ in pairs(AnimEditor.waves) do allNames[name] = true end
	for name, _ in pairs(allNames) do
		models[name] = {
			keyframes = AnimEditor.keyframesData[name] or {},
			waves = AnimEditor.waves[name] or {}
		}
	end

	local data = {
		modelFilename = AnimEditor.modelFilename,
		models = models
	}

	local pageId = invokeProgram(MAIN_PROGRAMID, "getCurrentPage")
	local dataStr = Utils.serializeTable(data)
	saveFile(AnimEditor.assetName, pageId, dataStr)

	AnimEditor.dirty = false
	_updateViewTitle()

	invokeProgram(MAIN_PROGRAMID, "refreshAssetIcons")
end

function renamedAsset(oldName, newName)
end

function _updateMouseInput()
	if AnimEditor.mouseDown == -1 then
		AnimEditor.mousePressedInViewport = false
	end

	if AnimEditor.mouseClickStartedOutside == true then
		if getMouseButtonDown(MOUSE_BUTTON_LEFT) == false and
			getMouseButtonDown(MOUSE_BUTTON_MIDDLE) == false and
			getMouseButtonDown(MOUSE_BUTTON_RIGHT) == false then
			AnimEditor.mouseClickStartedOutside = false
		end
		return
	end

	AnimEditor.mousePos = getMousePosition()

	AnimEditor.mouseInViewport = intersectRect(AnimEditor.mousePos,
		vec2(AnimEditor.viewSpriteData.position.x, AnimEditor.viewSpriteData.position.y),
		AnimEditor.viewSpriteData.size)

	AnimEditor.mousePressed = -1
	AnimEditor.mouseReleased = -1
	AnimEditor.mouseDown = -1

	if getMouseButtonPressed(MOUSE_BUTTON_LEFT) == true then
		if AnimEditor.mousePos.x > AnimEditor.screenWidth or
			AnimEditor.mousePos.y > AnimEditor.screenHeight then
			AnimEditor.mouseClickStartedOutside = true
		end
		AnimEditor.mousePressed = MOUSE_BUTTON_LEFT
	elseif getMouseButtonPressed(MOUSE_BUTTON_MIDDLE) == true then
		AnimEditor.mousePressed = MOUSE_BUTTON_MIDDLE
	elseif getMouseButtonPressed(MOUSE_BUTTON_RIGHT) == true then
		AnimEditor.mousePressed = MOUSE_BUTTON_RIGHT
	end

	if getMouseButtonReleased(MOUSE_BUTTON_LEFT) == true then
		AnimEditor.mouseReleased = MOUSE_BUTTON_LEFT
	elseif getMouseButtonReleased(MOUSE_BUTTON_MIDDLE) == true then
		AnimEditor.mouseReleased = MOUSE_BUTTON_MIDDLE
	elseif getMouseButtonReleased(MOUSE_BUTTON_RIGHT) == true then
		AnimEditor.mouseReleased = MOUSE_BUTTON_RIGHT
	end

	if getMouseButtonDown(MOUSE_BUTTON_LEFT) == true then
		AnimEditor.mouseDown = MOUSE_BUTTON_LEFT
	elseif getMouseButtonDown(MOUSE_BUTTON_MIDDLE) == true then
		AnimEditor.mouseDown = MOUSE_BUTTON_MIDDLE
	elseif getMouseButtonDown(MOUSE_BUTTON_RIGHT) == true then
		AnimEditor.mouseDown = MOUSE_BUTTON_RIGHT
	end

	if AnimEditor.mousePressed ~= -1 and UILib.isHovered() == true then
		AnimEditor.mousePressed = -1
		AnimEditor.mousePressedOnUI = true
	end

	if AnimEditor.mousePressedOnUI == true then
		AnimEditor.mouseDown = -1
	end

	if AnimEditor.mouseReleased ~= -1 and AnimEditor.mousePressedOnUI == true then
		AnimEditor.mousePressedOnUI = false
		AnimEditor.mouseReleased = -1
	end

	if AnimEditor.mousePressed ~= -1 then
		AnimEditor.mousePressedPos = vec2(AnimEditor.mousePos.x, AnimEditor.mousePos.y)
	end

	if AnimEditor.mouseDown == -1 and AnimEditor.mouseReleased == -1 then
		AnimEditor.manipulatingCamera = false
	end
end

function AnimEditor.switchCameraPerspective()
	if AnimEditor.cameraType == CAMERA_PERSPECTIVE then
		_setCameraType(CAMERA_ORTHO)
	else
		_setCameraType(CAMERA_PERSPECTIVE)
	end
end

function _setCameraType(cameraType)
	local camManip = AnimEditor.camManip
	local tanHalfFov = math.tan(AnimEditor.camFov / 2.0)
	local forward = vec3(0.0, 0.0, 1.0) * camManip.rot

	if cameraType == CAMERA_ORTHO then
		local focusPoint = camManip.pos - (forward * camManip.focusDist)
		AnimEditor.cameraType = CAMERA_ORTHO
		camManip.ortho = true
		camManip.perspFocusDist = camManip.focusDist
		camManip.focusDist = clamp(1.0 / (camManip.perspFocusDist * tanHalfFov), AnimEditor.orthoCamZoomMin, AnimEditor.orthoCamZoomMax)
	else
		if camManip.perspFocusDist == nil then
			camManip.perspFocusDist = 5.0
			camManip.focusDist = 0.25
		end

		local focusPoint = camManip.pos - (forward * camManip.perspFocusDist)
		AnimEditor.cameraType = CAMERA_PERSPECTIVE
		camManip.ortho = false
		camManip.focusDist = clamp(1.0 / (camManip.focusDist * tanHalfFov), 1.0, 50.0)
		camManip.perspFocusDist = nil
		camManip.pos = focusPoint + (forward * camManip.focusDist)
	end

	AnimEditor.cameraPerspButton.text = camManip.ortho and "o" or "p"
	AnimEditor.cameraPerspButton.changed = true

	Utils.setAppVar("cameraType", AnimEditor.cameraType)

	invokeProgram(MAIN_PROGRAMID, "refreshModelVisualizer")
end

function _cameraManipRot()
	if UILib.isHovered() == true then return end
	local mouseDelta = AnimEditor.mousePressedPos - AnimEditor.mousePos
	if vec2Length(mouseDelta) > 0.0 then
		AnimEditor.manipulatingCamera = true
		local camManip = AnimEditor.camManip
		local forward = vec3(0.0, 0.0, 1.0) * camManip.rot
		local orbitDist = camManip.ortho and camManip.perspFocusDist or camManip.focusDist
		local focusPoint = camManip.pos - (forward * orbitDist)
		camManip.rot = eulerRot(mouseDelta.y, 0.0, 0.0) * camManip.rot * eulerRot(0.0, mouseDelta.x, 0.0)
		local newForward = vec3(0.0, 0.0, 1.0) * camManip.rot
		camManip.pos = focusPoint + (newForward * orbitDist)
		AnimEditor.mousePressedPos = vec2(AnimEditor.mousePos.x, AnimEditor.mousePos.y)
	end
end

function _cameraManipPan()
	if UILib.isHovered() == true then return end

	local mouseDelta = AnimEditor.mousePressedPos - AnimEditor.mousePos
	if vec2Length(mouseDelta) > 0.0 then
		AnimEditor.manipulatingCamera = true
		local camManip = AnimEditor.camManip
		local p1 = screenToWorld(AnimEditor.mousePressedPos, AnimEditor.screenSize, camManip.focusDist, AnimEditor.camMat, AnimEditor.camFov, camManip.ortho)
		local p2 = screenToWorld(AnimEditor.mousePos, AnimEditor.screenSize, camManip.focusDist, AnimEditor.camMat, AnimEditor.camFov, camManip.ortho)
		if camManip.ortho == false then
			camManip.pos = camManip.pos + (p1 - p2)
		else
			camManip.pos = camManip.pos - (p1 - p2)
		end
		AnimEditor.mousePressedPos = vec2(AnimEditor.mousePos.x, AnimEditor.mousePos.y)
	end
end

function _cameraManipZoom()
	if UILib.isHovered() == true then return end

	local mouseDelta = AnimEditor.mousePressedPos - AnimEditor.mousePos
	if vec2Length(mouseDelta) > 0.0 then
		AnimEditor.manipulatingCamera = true
		local camManip = AnimEditor.camManip
		local deltaLen = mouseDelta.x
		if abs(mouseDelta.y) > abs(mouseDelta.x) then
			deltaLen = mouseDelta.y
		end
		local forward = vec3(0.0, 0.0, 1.0) * camManip.rot
		if camManip.ortho == false then
			local focusSpeed = 0.1
			local focusPoint = camManip.pos - (forward * camManip.focusDist)
			camManip.focusDist = clamp(camManip.focusDist + (deltaLen * focusSpeed), 1.0, 50.0)
			camManip.pos = focusPoint + (forward * camManip.focusDist)
		else
			local focusSpeed = 0.001
			camManip.focusDist = clamp(camManip.focusDist - (deltaLen * focusSpeed), AnimEditor.orthoCamZoomMin, AnimEditor.orthoCamZoomMax)
		end
		AnimEditor.mousePressedPos = vec2(AnimEditor.mousePos.x, AnimEditor.mousePos.y)
	end
end

function _updateCamera()
	if AnimEditor.mouseInViewport then
		local trackpadPan = getTrackpadPanned()
		if vec2Length(trackpadPan) > 0.0 then
			if UILib.isHovered() == false then
				AnimEditor.manipulatingCamera = true
				local camManip = AnimEditor.camManip
				local p1 = screenToWorld(AnimEditor.mousePos, AnimEditor.screenSize, camManip.focusDist, AnimEditor.camMat, AnimEditor.camFov, camManip.ortho)
				local p2 = screenToWorld(AnimEditor.mousePos - (trackpadPan * 10.0), AnimEditor.screenSize, camManip.focusDist, AnimEditor.camMat, AnimEditor.camFov, camManip.ortho)
				if camManip.ortho == false then
					camManip.pos = camManip.pos + (p1 - p2)
				else
					camManip.pos = camManip.pos - (p1 - p2)
				end
			end
		end

		local scrollWheel = getMouseWheelScrolled()
		if scrollWheel ~= 0.0 and UILib.isHovered() == false then
			AnimEditor.manipulatingCamera = true
			local camManip = AnimEditor.camManip
			local forward = vec3(0.0, 0.0, 1.0) * camManip.rot
			if camManip.ortho == false then
				local focusSpeed = 1.5
				local focusPoint = camManip.pos - (forward * camManip.focusDist)
				camManip.focusDist = clamp(camManip.focusDist + (scrollWheel * focusSpeed), 1.0, 50.0)
				camManip.pos = focusPoint + (forward * camManip.focusDist)
			else
				local focusSpeed = 0.05
				camManip.focusDist = clamp(camManip.focusDist - (scrollWheel * focusSpeed), AnimEditor.orthoCamZoomMin, AnimEditor.orthoCamZoomMax)
			end
		end

		local pinchZoom = getTrackpadPinchZoom()
		if pinchZoom ~= 0.0 and UILib.isHovered() == false then
			AnimEditor.manipulatingCamera = true
			local camManip = AnimEditor.camManip
			local forward = vec3(0.0, 0.0, 1.0) * camManip.rot
			if camManip.ortho == false then
				local focusSpeed = 1.5
				local focusPoint = camManip.pos - (forward * camManip.focusDist)
				camManip.focusDist = clamp(camManip.focusDist + (-pinchZoom * focusSpeed), 1.0, 50.0)
				camManip.pos = focusPoint + (forward * camManip.focusDist)
			else
				local focusSpeed = 0.05
				camManip.focusDist = clamp(camManip.focusDist - (-pinchZoom * focusSpeed), AnimEditor.orthoCamZoomMin, AnimEditor.orthoCamZoomMax)
			end
		end

		if AnimEditor.mouseDown == MOUSE_BUTTON_LEFT then
			_cameraManipRot()
		end

		if getMouseButtonDown(MOUSE_BUTTON_MIDDLE) == true then
			_cameraManipPan()
		end

		if getMouseButtonDown(MOUSE_BUTTON_RIGHT) == true then
			_cameraManipZoom()
		end
	end

	AnimEditor.camMat.position = AnimEditor.camManip.pos
	AnimEditor.camMat.rotation = AnimEditor.camManip.rot
	if AnimEditor.camManip.ortho == true then
		setCameraOrtho(AnimEditor.camMat, AnimEditor.camManip.focusDist)
	else
		setCamera(AnimEditor.camMat, AnimEditor.camFov)
	end
end

function _updateMeshLight()
	local lightDir = vec3Normalize(vec3(0.5, 1.0, 0.7))
	local lightColor = vec4(255.0, 255.0, 255.0, 255.0)
	local ambientColor = vec4(100.0, 100.0, 100.0, 255.0)

	local lightOnCamera = lightDir * AnimEditor.camManip.rot

	-- the uniforms are inert unless the mesh is drawn inside a lighting region, so keep them
	-- up to date regardless of the shading type: _drawPerspView decides whether lighting applies
	for i = 1, #AnimEditor.meshes do
		PolyUtil.meshLight(AnimEditor.meshes[i], lightOnCamera, lightColor, ambientColor)
	end
end

function _viewportToScreen(mousePos)
	local viewSpriteCenterX = AnimEditor.screenWidth * 0.5
	local viewSpriteLeft = viewSpriteCenterX - (AnimEditor.viewSpriteData.size.x * 0.5)
	local viewSpriteRight = viewSpriteCenterX + (AnimEditor.viewSpriteData.size.x * 0.5)

	local viewSpriteCenterY = AnimEditor.screenHeight * 0.5
	local viewSpriteTop = viewSpriteCenterY + (AnimEditor.viewSpriteData.size.y * 0.5)
	local viewSpriteBottom = viewSpriteCenterY - (AnimEditor.viewSpriteData.size.y * 0.5)

	local screenPoint = vec2(0.0, 0.0)
	screenPoint.x = remapRange(mousePos.x,
		AnimEditor.viewSpriteData.position.x, AnimEditor.viewSpriteData.position.x + AnimEditor.viewSpriteData.size.x,
		viewSpriteLeft, viewSpriteRight)
	screenPoint.y = remapRange(mousePos.y,
		AnimEditor.viewSpriteData.position.y, AnimEditor.viewSpriteData.position.y + AnimEditor.viewSpriteData.size.y,
		viewSpriteBottom, viewSpriteTop)

	return screenPoint
end

function _meshUnderCursor()
	if not AnimEditor.mouseInViewport then return -1 end
	if #AnimEditor.meshes == 0 then return -1 end

	local screenPos = _viewportToScreen(AnimEditor.mousePos)
	local camManip = AnimEditor.camManip
	local fov = camManip.ortho and -camManip.focusDist or AnimEditor.camFov
	local ray = screenRay(screenPos, fov, AnimEditor.camMat, camManip.ortho)

	local closestDist = math.huge
	local closestMesh = -1
	for i = 1, #AnimEditor.meshes do
		local bb = AnimEditor.meshBoundingBoxes[i]
		local transform = AnimEditor.meshTransforms[i]
		if bb ~= nil and transform ~= nil then
			local dist = intersectBox(ray.origin, ray.direction, bb.min, bb.max, transform)
			if dist > -1.0 and dist < closestDist then
				closestDist = dist
				closestMesh = i
			end
		end
	end
	return closestMesh
end

function _updateMeshHover()
	local prevHovered = AnimEditor.hoveredMesh
	AnimEditor.hoveredMesh = -1

	if AnimEditor.selectedMesh ~= -1 and AnimEditor.mouseInViewport and
		not AnimEditor.manipulatingCamera and not UILib.isHovered() then
		AnimEditor.hoveredMesh = _meshUnderCursor()
	end

	if AnimEditor.hoveredMesh ~= prevHovered then
		_updateHierarchySelection()
	end
end

function _updateSelection()
	if AnimEditor.mouseReleased ~= MOUSE_BUTTON_LEFT then return end
	if AnimEditor.manipulatingCamera then return end
	if not AnimEditor.mouseInViewport then return end
	if UILib.isSliderDragging() then return end

	local clickedMesh = _meshUnderCursor()
	if clickedMesh == AnimEditor.selectedMesh then return end

	_storeUndoState()
	if clickedMesh ~= -1 then
		AnimEditor.selectedMesh = clickedMesh
		_refreshProps()
		_setPropsEnabled(true)
	else
		AnimEditor.selectedMesh = -1
		_setPropsEnabled(false)
	end
	_updateHierarchySelection()
end

function _syncWaveToModel(idx)
	if AnimEditor.selectedMesh == -1 then return end
	local model = AnimEditor.meshModels[AnimEditor.selectedMesh]
	if model == nil then return end
	local name = getModelName(model)
	if AnimEditor.waves[name] == nil then return end
	AnimEditor.waves[name][idx].enabled = AnimEditor.wavePropEnabled[idx]
	AnimEditor.waves[name][idx].frequency = AnimEditor.waveFreqValues[idx]
	AnimEditor.waves[name][idx].amplitude = AnimEditor.waveAmpValues[idx]
end

function _refreshProps()
	if AnimEditor.selectedMesh == -1 then return end
	local model = AnimEditor.meshModels[AnimEditor.selectedMesh]
	if model == nil then return end

	local mat = getModelMatrix(model)
	local euler = quatToEuler(mat.rotation)

	AnimEditor.propValues[1] = mat.position.x
	AnimEditor.propValues[2] = mat.position.y
	AnimEditor.propValues[3] = mat.position.z
	AnimEditor.propValues[4] = euler.x
	AnimEditor.propValues[5] = euler.y
	AnimEditor.propValues[6] = euler.z
	AnimEditor.propValues[7] = mat.scale.x
	AnimEditor.propValues[8] = mat.scale.y
	AnimEditor.propValues[9] = mat.scale.z

	for i = 1, #AnimEditor.propSliders do
		AnimEditor.propSliders[i]:setValue(AnimEditor.propValues[i])
	end

	local name = getModelName(model)
	local waveData = AnimEditor.waves[name]
	if waveData ~= nil then
		for i = 1, 9 do
			AnimEditor.wavePropEnabled[i] = waveData[i].enabled
			AnimEditor.waveFreqValues[i] = waveData[i].frequency
			AnimEditor.waveAmpValues[i] = waveData[i].amplitude
			UILib.setButtonPressed(AnimEditor.waveProps[i], AnimEditor.wavePropEnabled[i])
			if not AnimEditor.waveProps[i].freqSlider.field.editing then
				AnimEditor.waveProps[i].freqSlider:setValue(AnimEditor.waveFreqValues[i])
			end
			if not AnimEditor.waveProps[i].ampSlider.field.editing then
				AnimEditor.waveProps[i].ampSlider:setValue(AnimEditor.waveAmpValues[i])
			end
			_updateWaveFieldState(i)
		end
	end
end

function _insertKeyframe(keyframeList, normalizedTime, values)
	local entry = {normalizedTime}
	for i = 1, #values do entry[i + 1] = values[i] end

	for i = 1, #keyframeList do
		if math.abs(keyframeList[i][1] - normalizedTime) < 0.0001 then
			keyframeList[i] = entry
			return
		end
		if keyframeList[i][1] > normalizedTime then
			table.insert(keyframeList, i, entry)
			return
		end
	end
	keyframeList[#keyframeList + 1] = entry
end

function _removeKeyframeAtNormalizedTime(keyframeList, normalizedTime)
	for i = 1, #keyframeList do
		if math.abs(keyframeList[i][1] - normalizedTime) < 0.0001 then
			table.remove(keyframeList, i)
			return true
		end
	end
	return false
end

function _hasKeyframeAtNormalizedTime(keyframeList, normalizedTime)
	for i = 1, #keyframeList do
		if math.abs(keyframeList[i][1] - normalizedTime) < 0.0001 then
			return true
		end
	end
	return false
end

function _getNormalizedTime()
	local totalTime = AnimEditor.framesCount / 30.0
	return AnimEditor.currentTime / totalTime
end

function _storeKeyframe()
	local normalizedTime = _getNormalizedTime()
	for meshIndex = 1, #AnimEditor.meshModels do
		local model = AnimEditor.meshModels[meshIndex]
		if model ~= nil then
			local name = getModelName(model)
			if AnimEditor.keyframesData[name] == nil then
				AnimEditor.keyframesData[name] = {}
			end

			local mat = getModelMatrix(model)
			local euler = quatToEuler(mat.rotation)

			_insertKeyframe(AnimEditor.keyframesData[name], normalizedTime, {
				mat.position.x, mat.position.y, mat.position.z,
				euler.x, euler.y, euler.z,
				mat.scale.x, mat.scale.y, mat.scale.z
			})
		end
	end
end

function _applyPropsToModel()
	if AnimEditor.selectedMesh == -1 then return end
	local model = AnimEditor.meshModels[AnimEditor.selectedMesh]
	if model == nil then return end

	local mat = mat4()
	mat.position = vec3(AnimEditor.propValues[1], AnimEditor.propValues[2], AnimEditor.propValues[3])
	mat.rotation = eulerRot(AnimEditor.propValues[4], AnimEditor.propValues[5], AnimEditor.propValues[6])
	mat.scale = vec3(AnimEditor.propValues[7], AnimEditor.propValues[8], AnimEditor.propValues[9])
	setModelMatrix(model, mat)

	_refreshAllBoundingBoxes()

	_storeKeyframe()
	_refreshTimelineKeyframes()
end

function _updateCurrentTimeSpriteColor()
	local hasKeyframe = false
	local normalizedTime = _getNormalizedTime()

	for name, keyframes in pairs(AnimEditor.keyframesData) do
		if _hasKeyframeAtNormalizedTime(keyframes, normalizedTime) then
			hasKeyframe = true
			break
		end
	end

	if hasKeyframe then
		AnimEditor.currentTimeSpriteData.color = Config.uiColors.animEditorKeyframeAtCurrentTime
	else
		AnimEditor.currentTimeSpriteData.color = Config.uiColors.animEditorCurrentFrame
	end
	setSprite(AnimEditor.currentTimeSprite, AnimEditor.currentTimeSpriteData)
end

function _refreshTimelineKeyframes()
	for i = 1, #AnimEditor.timelineFrames do
		AnimEditor.timelineFrames[i].color = Config.uiColors.animEditorTimelineFrame
		setSprite(AnimEditor.timelineFrames[i].sprite, AnimEditor.timelineFrames[i])
	end

	for name, keyframes in pairs(AnimEditor.keyframesData) do
		for _, kf in ipairs(keyframes) do
			local tickIndex = round(kf[1] * AnimEditor.framesCount) + 1
			if tickIndex >= 1 and tickIndex <= #AnimEditor.timelineFrames then
				AnimEditor.timelineFrames[tickIndex].color = Config.uiColors.animEditorKeyframe
				setSprite(AnimEditor.timelineFrames[tickIndex].sprite, AnimEditor.timelineFrames[tickIndex])
			end
		end
	end

	_updateCurrentTimeSpriteColor()
end

function _setPropsEnabled(enabled)
	AnimEditor.propsEnabled = enabled
	local axisColors = {Config.paletteNames.red, Config.paletteNames.green, Config.paletteNames.blue}

	for i = 1, #AnimEditor.propSliders do
		local slider = AnimEditor.propSliders[i]
		local field = slider.field
		local axisColor = axisColors[((i - 1) % 3) + 1]

		if enabled then
			field.hoverEnabled = true
			field.colorBase = axisColor * 0.5
			field.color = axisColor * 0.5
			field.colorHover = axisColor + 10
			field.textColor = Config.uiColors.animEditorPropFieldText
		else
			field.hoverEnabled = false
			field.colorBase = axisColor * 0.1
			field.color = axisColor * 0.1
			field.textColor = Config.uiColors.animEditorPropFieldText * 0.3
		end
		field.changed = true

		if slider.label ~= nil then
			if enabled then
				slider.label.textColor = vec4Copy(axisColor)
			else
				slider.label.textColor = axisColor * 0.3
			end
			slider.label.changed = true
		end
	end

	for i = 1, #AnimEditor.waveProps do
		local waveButton = AnimEditor.waveProps[i]

		if enabled then
			waveButton.hoverEnabled = true
			waveButton.colorBase = vec4Copy(Config.uiColors.panel)
			waveButton.color = vec4Copy(Config.uiColors.panel)
			waveButton.iconSpriteData.color = vec4Copy(Config.uiColors.icon)
			setSprite(waveButton.iconSprite, waveButton.iconSpriteData)
			waveButton.changed = true

			if AnimEditor.wavePropEnabled[i] then
				UILib.setButtonPressed(waveButton, false)
				UILib.setButtonPressed(waveButton, true)
			end

			_updateWaveFieldState(i)
		else
			waveButton.hoverEnabled = false
			waveButton.colorBase = Config.uiColors.panel * 0.3
			waveButton.color = Config.uiColors.panel * 0.3
			waveButton.iconSpriteData.color = Config.uiColors.icon * 0.1
			setSprite(waveButton.iconSprite, waveButton.iconSpriteData)
			waveButton.changed = true

			local freqField = waveButton.freqSlider.field
			freqField.hoverEnabled = false
			freqField.colorBase = Config.uiColors.animEditorWaveField * 0.01
			freqField.color = Config.uiColors.animEditorWaveField * 0.01
			freqField.textColor = Config.uiColors.animEditorWaveFieldText * 0.1
			freqField.changed = true

			local ampField = waveButton.ampSlider.field
			ampField.hoverEnabled = false
			ampField.colorBase = Config.uiColors.animEditorWaveField * 0.01
			ampField.color = Config.uiColors.animEditorWaveField * 0.01
			ampField.textColor = Config.uiColors.animEditorWaveFieldText * 0.1
			ampField.changed = true
		end
	end
end

function _updateHierarchySelection()
	local function markNodes(entries)
		for i = 1, #entries do
			entries[i].selected = entries[i].meshIndex ~= nil and entries[i].meshIndex == AnimEditor.selectedMesh
			entries[i].hovered = entries[i].meshIndex ~= nil and entries[i].meshIndex == AnimEditor.hoveredMesh and entries[i].meshIndex ~= AnimEditor.selectedMesh
			if entries[i].children then
				markNodes(entries[i].children)
			end
		end
	end

	markNodes(AnimEditor.hierarchy)
	AnimEditor.hierarchyInspector:refresh()
end

function _onHierarchyNodeClicked(node)
	if node.meshIndex ~= nil and node.meshIndex ~= AnimEditor.selectedMesh then
		_storeUndoState()
		AnimEditor.selectedMesh = node.meshIndex
		_refreshProps()
		_setPropsEnabled(true)
		_updateHierarchySelection()
	end
end

function _drawBoundingBox(bb, transform, color)
	local min = bb.min
	local max = bb.max

	local corners = {
		transform * vec3(min.x, min.y, min.z),
		transform * vec3(max.x, min.y, min.z),
		transform * vec3(min.x, max.y, min.z),
		transform * vec3(max.x, max.y, min.z),
		transform * vec3(min.x, min.y, max.z),
		transform * vec3(max.x, min.y, max.z),
		transform * vec3(min.x, max.y, max.z),
		transform * vec3(max.x, max.y, max.z),
	}

	drawLine(corners[1], corners[2], color)
	drawLine(corners[1], corners[3], color)
	drawLine(corners[1], corners[5], color)

	drawLine(corners[8], corners[7], color)
	drawLine(corners[8], corners[6], color)
	drawLine(corners[8], corners[4], color)

	drawLine(corners[3], corners[4], color)
	drawLine(corners[3], corners[7], color)

	drawLine(corners[2], corners[4], color)
	drawLine(corners[2], corners[6], color)

	drawLine(corners[5], corners[6], color)
	drawLine(corners[5], corners[7], color)
end

function _drawPerspView()
	setBilinearInterpEnabled(AnimEditor.bilinearInterp)

	local viewSpriteCenterX = (AnimEditor.screenWidth * 0.5)
	local viewSpriteLeft = viewSpriteCenterX - (AnimEditor.viewSpriteData.size.x * 0.5)
	local viewSpriteCenterY = (AnimEditor.screenHeight * 0.5)
	local viewSpriteBottom = viewSpriteCenterY - (AnimEditor.viewSpriteData.size.y * 0.5)

	setSprite(AnimEditor.viewSprite, AnimEditor.viewSpriteData)
	startRenderToSprite(AnimEditor.viewSprite,
		vec2(viewSpriteLeft, viewSpriteBottom),
		AnimEditor.viewSpriteData.size)

	local useTexture = 0.0
	if AnimEditor.shadingType == 1 or AnimEditor.shadingType == 4 then
		useTexture = 1.0
	end

	local lit = AnimEditor.shadingType ~= 4

	if lit then
		PolyUtil.beginLighting()
	end

	for i = 1, #AnimEditor.meshes do
		setMeshMicroShader(AnimEditor.meshes[i], "useTexture", useTexture, MICROSHADER_UNIFORM)
		drawMesh(AnimEditor.meshes[i])
	end

	if lit then
		PolyUtil.endLighting()
	end

	if AnimEditor.selectedMesh ~= -1 then
		local bb = AnimEditor.meshBoundingBoxes[AnimEditor.selectedMesh]
		local transform = AnimEditor.meshTransforms[AnimEditor.selectedMesh]
		if bb ~= nil and transform ~= nil then
			_drawBoundingBox(bb, transform, Config.uiColors.animEditorSelected)
		end
	end

	if AnimEditor.hoveredMesh ~= -1 and AnimEditor.hoveredMesh ~= AnimEditor.selectedMesh then
		local bb = AnimEditor.meshBoundingBoxes[AnimEditor.hoveredMesh]
		local transform = AnimEditor.meshTransforms[AnimEditor.hoveredMesh]
		_drawBoundingBox(bb, transform, Config.uiColors.animEditorPickHighlight)
	end

	setBilinearInterpEnabled(false)
end


function _findModelByName(name)
	for i = 1, #AnimEditor.meshModels do
		if getModelName(AnimEditor.meshModels[i]) == name then
			return AnimEditor.meshModels[i]
		end
	end
	return nil
end


function _updateModelMesh(model)
	local meshIndex = nil
	for i = 1, #AnimEditor.meshModels do
		if AnimEditor.meshModels[i] == model then
			meshIndex = i
			break
		end
	end
	if meshIndex ~= nil then
		local mesh = AnimEditor.meshes[meshIndex]
		local worldMat = getModelWorldMatrix(model)
		setMeshMicroShader(mesh, "modelMat", worldMat, MICROSHADER_UNIFORM)
		AnimEditor.meshBoundingBoxes[meshIndex] = computeWorldBoundingBox(mesh)
		AnimEditor.meshTransforms[meshIndex] = worldMat
	end
end

function _refreshAllBoundingBoxes()
	for i = 1, #AnimEditor.meshes do
		local mesh = AnimEditor.meshes[i]
		local model = AnimEditor.meshModels[i]
		if mesh ~= nil and model ~= nil then
			local worldMat = getModelWorldMatrix(model)
			setMeshMicroShader(mesh, "modelMat", worldMat, MICROSHADER_UNIFORM)
			AnimEditor.meshBoundingBoxes[i] = computeWorldBoundingBox(mesh)
			AnimEditor.meshTransforms[i] = worldMat
		end
	end
end

function _applyAnimPose()
	local normalizedTime = _getNormalizedTime()
	local processed = {}

	for name, keyframes in pairs(AnimEditor.keyframesData) do
		if #keyframes > 0 then
			local model = _findModelByName(name)
			if model ~= nil then
				processed[name] = true
				local wave = AnimEditor.waves[name]
				animPose(model, {keyframes = keyframes, waves = wave}, normalizedTime, 1.0)
				_updateModelMesh(model)
			end
		end
	end

	for name, wave in pairs(AnimEditor.waves) do
		if not processed[name] then
			local hasWave = false
			for i = 1, 9 do
				if wave[i].enabled then
					hasWave = true
					break
				end
			end
			if hasWave then
				local model = _findModelByName(name)
				if model ~= nil then
					animPose(model, {keyframes = {}, waves = wave}, normalizedTime, 1.0)
					_updateModelMesh(model)
				end
			end
		end
	end

	if AnimEditor.selectedMesh ~= -1 then
		_refreshProps()
	end
end

function _updateTimestamp()
	AnimEditor.timestampSpriteData.text = string.format("%.2f #%02d", AnimEditor.currentTime, AnimEditor.currentFrame)
	setSprite(AnimEditor.timestampSprite, AnimEditor.timestampSpriteData)
end

function _setCurrentFrame(frameIndex)
	if frameIndex == AnimEditor.currentFrame then return end
	AnimEditor.currentFrame = frameIndex
	AnimEditor.currentTime = (1.0 / 30.0) * frameIndex
	AnimEditor.currentTimeSpriteData.position.x = frameIndex * 4
	_updateCurrentTimeSpriteColor()
	setSprite(AnimEditor.currentTimeSprite, AnimEditor.currentTimeSpriteData)
	_applyAnimPose()
	_updateTimestamp()
end

function _updateTimeline()
	drawSprite2D(AnimEditor.timelineSprite)

	if AnimEditor.mousePressed == MOUSE_BUTTON_LEFT and
		intersectRect(AnimEditor.mousePos, vec2(AnimEditor.timelineSpriteData.position.x, AnimEditor.timelineSpriteData.position.y), AnimEditor.timelineSpriteData.size) then
		AnimEditor.draggingTimeline = true
		_storeUndoState()
	end

	if AnimEditor.draggingTimeline then
		if AnimEditor.mouseDown == MOUSE_BUTTON_LEFT then
			local relX = AnimEditor.mousePos.x - AnimEditor.timelineSpriteData.position.x
			local frameIndex = math.floor(relX / 4)
			frameIndex = math.max(0, math.min(frameIndex, AnimEditor.framesCount - 1))
			_setCurrentFrame(frameIndex)
		else
			AnimEditor.draggingTimeline = false
		end
	end

	for i = 1, #AnimEditor.timelineFrames do
		drawSprite2D(AnimEditor.timelineFrames[i].sprite)
	end

	drawSprite2D(AnimEditor.currentTimeSprite)
end

function _deepCopyKeyframesData()
	local copy = {}
	for name, keyframes in pairs(AnimEditor.keyframesData) do
		local kfCopy = {}
		for i = 1, #keyframes do
			local entry = keyframes[i]
			local entryCopy = {}
			for j = 1, #entry do
				entryCopy[j] = entry[j]
			end
			kfCopy[i] = entryCopy
		end
		copy[name] = kfCopy
	end
	return copy
end

function _storeUndoState()
	local entry = {}
	entry.keyframesData = _deepCopyKeyframesData()
	entry.currentTime = AnimEditor.currentTime
	entry.currentFrame = AnimEditor.currentFrame
	entry.selectedMesh = AnimEditor.selectedMesh

	entry.modelTransforms = {}
	for i = 1, #AnimEditor.meshModels do
		local model = AnimEditor.meshModels[i]
		if model ~= nil then
			entry.modelTransforms[i] = getModelMatrix(model)
		end
	end

	AnimEditor.undoStackId = AnimEditor.undoStackId + 1
	AnimEditor.undoStack[AnimEditor.undoStackId] = entry

	for i = AnimEditor.undoStackId + 1, #AnimEditor.undoStack do
		AnimEditor.undoStack[i] = nil
	end

	AnimEditor.dirty = true

	_updateViewTitle()
end

function _restoreUndoEntry(entry)
	AnimEditor.keyframesData = {}
	for name, keyframes in pairs(entry.keyframesData) do
		local kfCopy = {}
		for i = 1, #keyframes do
			local entryCopy = {}
			for j = 1, #keyframes[i] do
				entryCopy[j] = keyframes[i][j]
			end
			kfCopy[i] = entryCopy
		end
		AnimEditor.keyframesData[name] = kfCopy
	end
	
	_setCurrentFrame(entry.currentFrame)

	for i = 1, #AnimEditor.meshModels do
		local model = AnimEditor.meshModels[i]
		local storedMat = entry.modelTransforms[i]
		
		if model ~= nil and storedMat ~= nil then
			setModelMatrix(model, storedMat)
			local mesh = AnimEditor.meshes[i]
			if mesh ~= nil then
				local worldMat = getModelWorldMatrix(model)
				setMeshMicroShader(mesh, "modelMat", worldMat, MICROSHADER_UNIFORM)
				AnimEditor.meshBoundingBoxes[i] = computeWorldBoundingBox(mesh)
				AnimEditor.meshTransforms[i] = worldMat
			end
		end
	end

	AnimEditor.selectedMesh = entry.selectedMesh
	if AnimEditor.selectedMesh ~= -1 then
		_refreshProps()
		_setPropsEnabled(true)
	else
		_setPropsEnabled(false)
	end
	_updateHierarchySelection()
	_refreshTimelineKeyframes()
end

function _undo()
	AnimEditor.undoStackId = AnimEditor.undoStackId - 1
	if AnimEditor.undoStackId < 1 then
		AnimEditor.undoStackId = 1
		return
	end
	_restoreUndoEntry(AnimEditor.undoStack[AnimEditor.undoStackId])
end

function _redo()
	AnimEditor.undoStackId = AnimEditor.undoStackId + 1
	local entry = AnimEditor.undoStack[AnimEditor.undoStackId]
	if entry == nil then
		AnimEditor.undoStackId = AnimEditor.undoStackId - 1
		return
	end
	_restoreUndoEntry(entry)
end

function shortcutUndo()
	_undo()
end

function shortcutRedo()
	_redo()
end

function shortcutPlay()
	_togglePlay()
end

function _deleteKeyframeAtCurrentFrame()
	local changed = false
	local normalizedTime = _getNormalizedTime()
	for name, keyframes in pairs(AnimEditor.keyframesData) do
		if _removeKeyframeAtNormalizedTime(keyframes, normalizedTime) then
			changed = true
		end
	end
	if changed then
		_refreshTimelineKeyframes()
		_applyAnimPose()
		_storeUndoState()
	end
end

function _updateStatusBar()
	local desc = nil

	local hovered = UILib.hoveredSpriteWidget
	if hovered ~= nil and hovered.desc ~= nil then
		if AnimEditor.statusText ~= hovered.desc then
			desc = hovered.desc
			AnimEditor.statusText = desc

			local shortcut = Utils.registeredShortcuts[desc]
			if shortcut ~= nil then
				if shortcut.modifier ~= nil then
					desc = desc .. " - ( " .. shortcut.modifier .. " + " .. shortcut.key .. " )"
				else
					desc = desc .. " - ( " .. shortcut.key .. " )"
				end
			end

			AnimEditor.descPanel.text = desc
			AnimEditor.descPanel.visible = true
			AnimEditor.descPanel.changed = true
		end
	else
		if AnimEditor.descPanel.visible == true then
			AnimEditor.descPanel.text = ""
			AnimEditor.statusText = ""
			AnimEditor.descPanel.visible = false
			AnimEditor.descPanel.changed = true
		end
	end
end

function update()
	_updateMouseInput()
	_updateMeshHover()
	_updateSelection()
	_updateMeshLight()

	if AnimEditor.isPlaying then
		_playAnim()
	end

	if getKeyPressed(KEY_BACKSPACE) or getKeyPressed(KEY_DELETE) then
		_deleteKeyframeAtCurrentFrame()
	end

	drawSprite2D(AnimEditor.backgroundSprite)

	_updateCamera()
	_drawPerspView()

	if AnimEditor.ditherEnabled == true then
		setDitherEnabled(true)
	end

	drawSprite2D(AnimEditor.viewSprite)

	if AnimEditor.ditherEnabled == true then
		setDitherEnabled(false)
	end

	drawSprite2D(AnimEditor.controlBarSprite)
	drawSprite2D(AnimEditor.timestampSprite)
	_updateTimeline()
	_updateStatusBar()

	UILib.update()
end
